mirror of https://github.com/dcoredump/dexed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3516 lines
120 KiB
3516 lines
120 KiB
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2015 - ROLI Ltd.
|
|
|
|
Permission is granted to use this software under the terms of either:
|
|
a) the GPL v2 (or any later version)
|
|
b) the Affero GPL v3
|
|
|
|
Details of these licenses can be found at: www.gnu.org/licenses
|
|
|
|
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.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.juce.com for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#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 GET_APPCOMMAND_LPARAM(lParam) ((short) (HIWORD (lParam) & ~FAPPCOMMAND_MASK))
|
|
|
|
#define FAPPCOMMAND_MASK 0xF000
|
|
#define APPCOMMAND_MEDIA_NEXTTRACK 11
|
|
#define APPCOMMAND_MEDIA_PREVIOUSTRACK 12
|
|
#define APPCOMMAND_MEDIA_STOP 13
|
|
#define APPCOMMAND_MEDIA_PLAY_PAUSE 14
|
|
#endif
|
|
|
|
#ifndef WM_APPCOMMAND
|
|
#define WM_APPCOMMAND 0x0319
|
|
#endif
|
|
|
|
extern void juce_repeatLastProcessPriority();
|
|
extern void juce_checkCurrentlyFocusedTopLevelWindow(); // in juce_TopLevelWindow.cpp
|
|
extern bool juce_isRunningInWine();
|
|
|
|
typedef bool (*CheckEventBlockedByModalComps) (const MSG&);
|
|
extern CheckEventBlockedByModalComps isEventBlockedByModalComps;
|
|
|
|
static bool shouldDeactivateTitleBar = true;
|
|
|
|
extern void* getUser32Function (const char*);
|
|
|
|
//==============================================================================
|
|
typedef BOOL (WINAPI* UpdateLayeredWinFunc) (HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);
|
|
static UpdateLayeredWinFunc updateLayeredWindow = nullptr;
|
|
|
|
bool Desktop::canUseSemiTransparentWindows() noexcept
|
|
{
|
|
if (updateLayeredWindow == nullptr && ! juce_isRunningInWine())
|
|
updateLayeredWindow = (UpdateLayeredWinFunc) getUser32Function ("UpdateLayeredWindow");
|
|
|
|
return updateLayeredWindow != nullptr;
|
|
}
|
|
|
|
//==============================================================================
|
|
#ifndef WM_TOUCH
|
|
#define WM_TOUCH 0x0240
|
|
#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
|
|
#define TOUCHEVENTF_MOVE 0x0001
|
|
#define TOUCHEVENTF_DOWN 0x0002
|
|
#define TOUCHEVENTF_UP 0x0004
|
|
#define TOUCHEVENTF_PRIMARY 0x0010
|
|
DECLARE_HANDLE (HTOUCHINPUT);
|
|
DECLARE_HANDLE (HGESTUREINFO);
|
|
|
|
struct TOUCHINPUT
|
|
{
|
|
LONG x;
|
|
LONG y;
|
|
HANDLE hSource;
|
|
DWORD dwID;
|
|
DWORD dwFlags;
|
|
DWORD dwMask;
|
|
DWORD dwTime;
|
|
ULONG_PTR dwExtraInfo;
|
|
DWORD cxContact;
|
|
DWORD cyContact;
|
|
};
|
|
|
|
struct GESTUREINFO
|
|
{
|
|
UINT cbSize;
|
|
DWORD dwFlags;
|
|
DWORD dwID;
|
|
HWND hwndTarget;
|
|
POINTS ptsLocation;
|
|
DWORD dwInstanceID;
|
|
DWORD dwSequenceID;
|
|
ULONGLONG ullArguments;
|
|
UINT cbExtraArgs;
|
|
};
|
|
|
|
#endif
|
|
|
|
#ifndef MONITOR_DPI_TYPE
|
|
enum Monitor_DPI_Type
|
|
{
|
|
MDT_Effective_DPI = 0,
|
|
MDT_Angular_DPI = 1,
|
|
MDT_Raw_DPI = 2,
|
|
MDT_Default = MDT_Effective_DPI
|
|
};
|
|
|
|
enum Process_DPI_Awareness
|
|
{
|
|
Process_DPI_Unaware = 0,
|
|
Process_System_DPI_Aware = 1,
|
|
Process_Per_Monitor_DPI_Aware = 2
|
|
};
|
|
#endif
|
|
|
|
typedef BOOL (WINAPI* RegisterTouchWindowFunc) (HWND, ULONG);
|
|
typedef BOOL (WINAPI* GetTouchInputInfoFunc) (HTOUCHINPUT, UINT, TOUCHINPUT*, int);
|
|
typedef BOOL (WINAPI* CloseTouchInputHandleFunc) (HTOUCHINPUT);
|
|
typedef BOOL (WINAPI* GetGestureInfoFunc) (HGESTUREINFO, GESTUREINFO*);
|
|
typedef BOOL (WINAPI* SetProcessDPIAwareFunc)();
|
|
typedef BOOL (WINAPI* SetProcessDPIAwarenessFunc) (Process_DPI_Awareness);
|
|
typedef HRESULT (WINAPI* GetDPIForMonitorFunc) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*);
|
|
|
|
static RegisterTouchWindowFunc registerTouchWindow = nullptr;
|
|
static GetTouchInputInfoFunc getTouchInputInfo = nullptr;
|
|
static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr;
|
|
static GetGestureInfoFunc getGestureInfo = nullptr;
|
|
static SetProcessDPIAwareFunc setProcessDPIAware = nullptr;
|
|
static SetProcessDPIAwarenessFunc setProcessDPIAwareness = nullptr;
|
|
static GetDPIForMonitorFunc getDPIForMonitor = nullptr;
|
|
|
|
static bool hasCheckedForMultiTouch = false;
|
|
|
|
static bool canUseMultiTouch()
|
|
{
|
|
if (registerTouchWindow == nullptr && ! hasCheckedForMultiTouch)
|
|
{
|
|
hasCheckedForMultiTouch = true;
|
|
|
|
registerTouchWindow = (RegisterTouchWindowFunc) getUser32Function ("RegisterTouchWindow");
|
|
getTouchInputInfo = (GetTouchInputInfoFunc) getUser32Function ("GetTouchInputInfo");
|
|
closeTouchInputHandle = (CloseTouchInputHandleFunc) getUser32Function ("CloseTouchInputHandle");
|
|
getGestureInfo = (GetGestureInfoFunc) getUser32Function ("GetGestureInfo");
|
|
}
|
|
|
|
return registerTouchWindow != nullptr;
|
|
}
|
|
|
|
static Rectangle<int> rectangleFromRECT (const RECT& r) noexcept
|
|
{
|
|
return Rectangle<int>::leftTopRightBottom ((int) r.left, (int) r.top, (int) r.right, (int) r.bottom);
|
|
}
|
|
|
|
static void setWindowPos (HWND hwnd, Rectangle<int> bounds, UINT flags)
|
|
{
|
|
SetWindowPos (hwnd, 0, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), flags);
|
|
}
|
|
|
|
static RECT getWindowRect (HWND hwnd)
|
|
{
|
|
RECT r;
|
|
GetWindowRect (hwnd, &r);
|
|
return r;
|
|
}
|
|
|
|
static void setWindowZOrder (HWND hwnd, HWND insertAfter)
|
|
{
|
|
SetWindowPos (hwnd, insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
|
|
}
|
|
|
|
//==============================================================================
|
|
static void setDPIAwareness()
|
|
{
|
|
#if ! JUCE_DISABLE_WIN32_DPI_AWARENESS
|
|
if (JUCEApplicationBase::isStandaloneApp())
|
|
{
|
|
if (setProcessDPIAwareness == nullptr)
|
|
{
|
|
HMODULE shcoreModule = GetModuleHandleA ("SHCore.dll");
|
|
|
|
if (shcoreModule != 0)
|
|
{
|
|
setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness");
|
|
getDPIForMonitor = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor");
|
|
|
|
if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr
|
|
// && SUCCEEDED (setProcessDPIAwareness (Process_Per_Monitor_DPI_Aware)))
|
|
&& SUCCEEDED (setProcessDPIAwareness (Process_System_DPI_Aware))) // (keep using this mode temporarily..)
|
|
return;
|
|
}
|
|
|
|
if (setProcessDPIAware == nullptr)
|
|
{
|
|
setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware");
|
|
|
|
if (setProcessDPIAware != nullptr)
|
|
setProcessDPIAware();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static double getGlobalDPI()
|
|
{
|
|
setDPIAwareness();
|
|
|
|
HDC dc = GetDC (0);
|
|
const double dpi = (GetDeviceCaps (dc, LOGPIXELSX)
|
|
+ GetDeviceCaps (dc, LOGPIXELSY)) / 2.0;
|
|
ReleaseDC (0, dc);
|
|
return dpi;
|
|
}
|
|
|
|
double Desktop::getDefaultMasterScale()
|
|
{
|
|
return JUCEApplicationBase::isStandaloneApp() ? getGlobalDPI() / 96.0
|
|
: 1.0;
|
|
}
|
|
|
|
//==============================================================================
|
|
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
|
|
{
|
|
return upright;
|
|
}
|
|
|
|
int64 getMouseEventTime()
|
|
{
|
|
static int64 eventTimeOffset = 0;
|
|
static LONG lastMessageTime = 0;
|
|
const LONG thisMessageTime = GetMessageTime();
|
|
|
|
if (thisMessageTime < lastMessageTime || lastMessageTime == 0)
|
|
{
|
|
lastMessageTime = thisMessageTime;
|
|
eventTimeOffset = Time::currentTimeMillis() - thisMessageTime;
|
|
}
|
|
|
|
return eventTimeOffset + thisMessageTime;
|
|
}
|
|
|
|
//==============================================================================
|
|
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 ImagePixelData
|
|
{
|
|
public:
|
|
WindowsBitmapImage (const Image::PixelFormat format,
|
|
const int w, const int h, const bool clearImage)
|
|
: ImagePixelData (format, w, h)
|
|
{
|
|
jassert (format == Image::RGB || format == Image::ARGB);
|
|
|
|
static bool alwaysUse32Bits = isGraphicsCard32Bit(); // NB: for 32-bit cards, it's faster to use a 32-bit image.
|
|
|
|
pixelStride = (alwaysUse32Bits || format == Image::ARGB) ? 4 : 3;
|
|
lineStride = -((w * pixelStride + 3) & ~3);
|
|
|
|
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 == Image::ARGB)
|
|
{
|
|
bitmapInfo.bV4AlphaMask = 0xff000000;
|
|
bitmapInfo.bV4RedMask = 0xff0000;
|
|
bitmapInfo.bV4GreenMask = 0xff00;
|
|
bitmapInfo.bV4BlueMask = 0xff;
|
|
bitmapInfo.bV4V4Compression = BI_BITFIELDS;
|
|
}
|
|
else
|
|
{
|
|
bitmapInfo.bV4V4Compression = BI_RGB;
|
|
}
|
|
|
|
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);
|
|
|
|
previousBitmap = SelectObject (hdc, hBitmap);
|
|
|
|
if (format == Image::ARGB && clearImage)
|
|
zeromem (bitmapData, (size_t) std::abs (h * lineStride));
|
|
|
|
imageData = bitmapData - (lineStride * (h - 1));
|
|
}
|
|
|
|
~WindowsBitmapImage()
|
|
{
|
|
SelectObject (hdc, previousBitmap); // Selecting the previous bitmap before deleting the DC avoids a warning in BoundsChecker
|
|
DeleteDC (hdc);
|
|
DeleteObject (hBitmap);
|
|
}
|
|
|
|
ImageType* createType() const override { return new NativeImageType(); }
|
|
|
|
LowLevelGraphicsContext* createLowLevelContext() override
|
|
{
|
|
sendDataChangeMessage();
|
|
return new LowLevelGraphicsSoftwareRenderer (Image (this));
|
|
}
|
|
|
|
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
|
|
{
|
|
bitmap.data = imageData + x * pixelStride + y * lineStride;
|
|
bitmap.pixelFormat = pixelFormat;
|
|
bitmap.lineStride = lineStride;
|
|
bitmap.pixelStride = pixelStride;
|
|
|
|
if (mode != Image::BitmapData::readOnly)
|
|
sendDataChangeMessage();
|
|
}
|
|
|
|
ImagePixelData* clone() override
|
|
{
|
|
WindowsBitmapImage* im = new WindowsBitmapImage (pixelFormat, width, height, false);
|
|
|
|
for (int i = 0; i < height; ++i)
|
|
memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride);
|
|
|
|
return im;
|
|
}
|
|
|
|
void blitToWindow (HWND hwnd, HDC dc, const bool transparent,
|
|
const int x, const int y,
|
|
const uint8 updateLayeredWindowAlpha) noexcept
|
|
{
|
|
SetMapMode (dc, MM_TEXT);
|
|
|
|
if (transparent)
|
|
{
|
|
RECT windowBounds = getWindowRect (hwnd);
|
|
|
|
POINT p = { -x, -y };
|
|
POINT pos = { windowBounds.left, windowBounds.top };
|
|
SIZE size = { windowBounds.right - windowBounds.left,
|
|
windowBounds.bottom - windowBounds.top };
|
|
|
|
BLENDFUNCTION bf;
|
|
bf.AlphaFormat = 1 /*AC_SRC_ALPHA*/;
|
|
bf.BlendFlags = 0;
|
|
bf.BlendOp = AC_SRC_OVER;
|
|
bf.SourceConstantAlpha = updateLayeredWindowAlpha;
|
|
|
|
updateLayeredWindow (hwnd, 0, &pos, &size, hdc, &p, 0, &bf, 2 /*ULW_ALPHA*/);
|
|
}
|
|
else
|
|
{
|
|
StretchDIBits (dc,
|
|
x, y, width, height,
|
|
0, 0, width, height,
|
|
bitmapData, (const BITMAPINFO*) &bitmapInfo,
|
|
DIB_RGB_COLORS, SRCCOPY);
|
|
}
|
|
}
|
|
|
|
HBITMAP hBitmap;
|
|
HGDIOBJ previousBitmap;
|
|
BITMAPV4HEADER bitmapInfo;
|
|
HDC hdc;
|
|
uint8* bitmapData;
|
|
int pixelStride, lineStride;
|
|
uint8* imageData;
|
|
|
|
private:
|
|
static bool isGraphicsCard32Bit()
|
|
{
|
|
HDC dc = GetDC (0);
|
|
const int bitsPerPixel = GetDeviceCaps (dc, BITSPIXEL);
|
|
ReleaseDC (0, dc);
|
|
return bitsPerPixel > 24;
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsBitmapImage)
|
|
};
|
|
|
|
//==============================================================================
|
|
namespace IconConverters
|
|
{
|
|
Image createImageFromHBITMAP (HBITMAP bitmap)
|
|
{
|
|
Image im;
|
|
|
|
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 = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true);
|
|
Image::BitmapData imageData (im, Image::BitmapData::writeOnly);
|
|
|
|
for (int y = bm.bmHeight; --y >= 0;)
|
|
{
|
|
for (int x = bm.bmWidth; --x >= 0;)
|
|
{
|
|
COLORREF col = GetPixel (dc, x, y);
|
|
|
|
imageData.setPixelColour (x, y, Colour ((uint8) GetRValue (col),
|
|
(uint8) GetGValue (col),
|
|
(uint8) GetBValue (col)));
|
|
}
|
|
}
|
|
|
|
DeleteDC (dc);
|
|
}
|
|
}
|
|
|
|
return im;
|
|
}
|
|
|
|
Image createImageFromHICON (HICON icon)
|
|
{
|
|
ICONINFO info;
|
|
|
|
if (GetIconInfo (icon, &info))
|
|
{
|
|
Image mask (createImageFromHBITMAP (info.hbmMask));
|
|
Image image (createImageFromHBITMAP (info.hbmColor));
|
|
|
|
if (mask.isValid() && image.isValid())
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
}
|
|
|
|
return Image::null;
|
|
}
|
|
|
|
HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY)
|
|
{
|
|
WindowsBitmapImage* nativeBitmap = new WindowsBitmapImage (Image::ARGB, image.getWidth(), image.getHeight(), true);
|
|
Image bitmap (nativeBitmap);
|
|
|
|
{
|
|
Graphics g (bitmap);
|
|
g.drawImageAt (image, 0, 0);
|
|
}
|
|
|
|
HBITMAP mask = CreateBitmap (image.getWidth(), image.getHeight(), 1, 1, 0);
|
|
|
|
ICONINFO info;
|
|
info.fIcon = isIcon;
|
|
info.xHotspot = (DWORD) hotspotX;
|
|
info.yHotspot = (DWORD) hotspotY;
|
|
info.hbmMask = mask;
|
|
info.hbmColor = nativeBitmap->hBitmap;
|
|
|
|
HICON hi = CreateIconIndirect (&info);
|
|
DeleteObject (mask);
|
|
return hi;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
class HWNDComponentPeer : public ComponentPeer
|
|
{
|
|
public:
|
|
enum RenderingEngineType
|
|
{
|
|
softwareRenderingEngine = 0,
|
|
direct2DRenderingEngine
|
|
};
|
|
|
|
//==============================================================================
|
|
HWNDComponentPeer (Component& comp, const int windowStyleFlags, HWND parent, bool nonRepainting)
|
|
: ComponentPeer (comp, windowStyleFlags),
|
|
dontRepaint (nonRepainting),
|
|
parentToAddTo (parent),
|
|
currentRenderingEngine (softwareRenderingEngine),
|
|
lastPaintTime (0),
|
|
lastMagnifySize (0),
|
|
fullScreen (false),
|
|
isDragging (false),
|
|
isMouseOver (false),
|
|
hasCreatedCaret (false),
|
|
constrainerIsResizing (false),
|
|
currentWindowIcon (0),
|
|
dropTarget (nullptr),
|
|
updateLayeredWindowAlpha (255)
|
|
{
|
|
callFunctionIfNotLocked (&createWindowCallback, this);
|
|
|
|
setTitle (component.getName());
|
|
|
|
if ((windowStyleFlags & windowHasDropShadow) != 0
|
|
&& Desktop::canUseSemiTransparentWindows()
|
|
&& ((! hasTitleBar()) || SystemStats::getOperatingSystemType() < SystemStats::WinVista))
|
|
{
|
|
shadower = component.getLookAndFeel().createDropShadowerForComponent (&component);
|
|
|
|
if (shadower != nullptr)
|
|
shadower->setOwner (&component);
|
|
}
|
|
}
|
|
|
|
~HWNDComponentPeer()
|
|
{
|
|
shadower = nullptr;
|
|
|
|
// do this before the next bit to avoid messages arriving for this window
|
|
// before it's destroyed
|
|
JuceWindowIdentifier::setAsJUCEWindow (hwnd, false);
|
|
|
|
callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd);
|
|
|
|
if (currentWindowIcon != 0)
|
|
DestroyIcon (currentWindowIcon);
|
|
|
|
if (dropTarget != nullptr)
|
|
{
|
|
dropTarget->clear();
|
|
dropTarget->Release();
|
|
dropTarget = nullptr;
|
|
}
|
|
|
|
#if JUCE_DIRECT2D
|
|
direct2DContext = nullptr;
|
|
#endif
|
|
}
|
|
|
|
//==============================================================================
|
|
void* getNativeHandle() const override { return hwnd; }
|
|
|
|
void setVisible (bool shouldBeVisible) override
|
|
{
|
|
ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
|
|
|
if (shouldBeVisible)
|
|
InvalidateRect (hwnd, 0, 0);
|
|
else
|
|
lastPaintTime = 0;
|
|
}
|
|
|
|
void setTitle (const String& title) override
|
|
{
|
|
// Unfortunately some ancient bits of win32 mean you can only perform this operation from the message thread.
|
|
jassert (MessageManager::getInstance()->isThisTheMessageThread());
|
|
|
|
SetWindowText (hwnd, title.toWideCharPointer());
|
|
}
|
|
|
|
void repaintNowIfTransparent()
|
|
{
|
|
if (isUsingUpdateLayeredWindow() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30)
|
|
handlePaintMessage();
|
|
}
|
|
|
|
void updateBorderSize()
|
|
{
|
|
WINDOWINFO info;
|
|
info.cbSize = sizeof (info);
|
|
|
|
if (GetWindowInfo (hwnd, &info))
|
|
windowBorder = BorderSize<int> (info.rcClient.top - info.rcWindow.top,
|
|
info.rcClient.left - info.rcWindow.left,
|
|
info.rcWindow.bottom - info.rcClient.bottom,
|
|
info.rcWindow.right - info.rcClient.right);
|
|
|
|
#if JUCE_DIRECT2D
|
|
if (direct2DContext != nullptr)
|
|
direct2DContext->resized();
|
|
#endif
|
|
}
|
|
|
|
void setBounds (const Rectangle<int>& bounds, bool isNowFullScreen) override
|
|
{
|
|
fullScreen = isNowFullScreen;
|
|
|
|
Rectangle<int> newBounds (windowBorder.addedTo (bounds));
|
|
|
|
if (isUsingUpdateLayeredWindow())
|
|
{
|
|
if (HWND parentHwnd = GetParent (hwnd))
|
|
{
|
|
RECT parentRect = getWindowRect (parentHwnd);
|
|
newBounds.translate (parentRect.left, parentRect.top);
|
|
}
|
|
}
|
|
|
|
const Rectangle<int> oldBounds (getBounds());
|
|
const bool hasMoved = (oldBounds.getPosition() != bounds.getPosition());
|
|
const bool hasResized = (oldBounds.getWidth() != bounds.getWidth()
|
|
|| oldBounds.getHeight() != bounds.getHeight());
|
|
|
|
DWORD flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
|
|
if (! hasMoved) flags |= SWP_NOMOVE;
|
|
if (! hasResized) flags |= SWP_NOSIZE;
|
|
|
|
setWindowPos (hwnd, newBounds, flags);
|
|
|
|
if (hasResized && isValidPeer (this))
|
|
{
|
|
updateBorderSize();
|
|
repaintNowIfTransparent();
|
|
}
|
|
}
|
|
|
|
Rectangle<int> getBounds() const override
|
|
{
|
|
Rectangle<int> bounds (rectangleFromRECT (getWindowRect (hwnd)));
|
|
|
|
if (HWND parentH = GetParent (hwnd))
|
|
{
|
|
RECT r = getWindowRect (parentH);
|
|
bounds.translate (-r.left, -r.top);
|
|
}
|
|
|
|
return windowBorder.subtractedFrom (bounds);
|
|
}
|
|
|
|
Point<int> getScreenPosition() const
|
|
{
|
|
RECT r = getWindowRect (hwnd);
|
|
|
|
return Point<int> (r.left + windowBorder.getLeft(),
|
|
r.top + windowBorder.getTop());
|
|
}
|
|
|
|
Point<float> localToGlobal (Point<float> relativePosition) override { return relativePosition + getScreenPosition().toFloat(); }
|
|
Point<float> globalToLocal (Point<float> screenPosition) override { return screenPosition - getScreenPosition().toFloat(); }
|
|
|
|
void setAlpha (float newAlpha) override
|
|
{
|
|
const uint8 intAlpha = (uint8) jlimit (0, 255, (int) (newAlpha * 255.0f));
|
|
|
|
if (component.isOpaque())
|
|
{
|
|
if (newAlpha < 1.0f)
|
|
{
|
|
SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
|
|
SetLayeredWindowAttributes (hwnd, RGB (0, 0, 0), intAlpha, LWA_ALPHA);
|
|
}
|
|
else
|
|
{
|
|
SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
|
|
RedrawWindow (hwnd, 0, 0, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
updateLayeredWindowAlpha = intAlpha;
|
|
component.repaint();
|
|
}
|
|
}
|
|
|
|
void setMinimised (bool shouldBeMinimised) override
|
|
{
|
|
if (shouldBeMinimised != isMinimised())
|
|
ShowWindow (hwnd, shouldBeMinimised ? SW_MINIMIZE : SW_SHOWNORMAL);
|
|
}
|
|
|
|
bool isMinimised() const override
|
|
{
|
|
WINDOWPLACEMENT wp;
|
|
wp.length = sizeof (WINDOWPLACEMENT);
|
|
GetWindowPlacement (hwnd, &wp);
|
|
|
|
return wp.showCmd == SW_SHOWMINIMIZED;
|
|
}
|
|
|
|
void setFullScreen (bool shouldBeFullScreen) override
|
|
{
|
|
setMinimised (false);
|
|
|
|
if (isFullScreen() != shouldBeFullScreen)
|
|
{
|
|
fullScreen = shouldBeFullScreen;
|
|
const WeakReference<Component> deletionChecker (&component);
|
|
|
|
if (! fullScreen)
|
|
{
|
|
const Rectangle<int> boundsCopy (lastNonFullscreenBounds);
|
|
|
|
if (hasTitleBar())
|
|
ShowWindow (hwnd, SW_SHOWNORMAL);
|
|
|
|
if (! boundsCopy.isEmpty())
|
|
setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, boundsCopy), false);
|
|
}
|
|
else
|
|
{
|
|
if (hasTitleBar())
|
|
ShowWindow (hwnd, SW_SHOWMAXIMIZED);
|
|
else
|
|
SendMessageW (hwnd, WM_SETTINGCHANGE, 0, 0);
|
|
}
|
|
|
|
if (deletionChecker != nullptr)
|
|
handleMovedOrResized();
|
|
}
|
|
}
|
|
|
|
bool isFullScreen() const override
|
|
{
|
|
if (! hasTitleBar())
|
|
return fullScreen;
|
|
|
|
WINDOWPLACEMENT wp;
|
|
wp.length = sizeof (wp);
|
|
GetWindowPlacement (hwnd, &wp);
|
|
|
|
return wp.showCmd == SW_SHOWMAXIMIZED;
|
|
}
|
|
|
|
bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
|
|
{
|
|
RECT r = getWindowRect (hwnd);
|
|
|
|
if (! (isPositiveAndBelow (localPos.x, (int) (r.right - r.left))
|
|
&& isPositiveAndBelow (localPos.y, (int) (r.bottom - r.top))))
|
|
return false;
|
|
|
|
POINT p = { localPos.x + r.left + windowBorder.getLeft(),
|
|
localPos.y + r.top + windowBorder.getTop() };
|
|
|
|
HWND w = WindowFromPoint (p);
|
|
return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0));
|
|
}
|
|
|
|
BorderSize<int> getFrameSize() const override
|
|
{
|
|
return windowBorder;
|
|
}
|
|
|
|
bool setAlwaysOnTop (bool alwaysOnTop) override
|
|
{
|
|
const bool oldDeactivate = shouldDeactivateTitleBar;
|
|
shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
|
|
|
|
setWindowZOrder (hwnd, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST);
|
|
|
|
shouldDeactivateTitleBar = oldDeactivate;
|
|
|
|
if (shadower != nullptr)
|
|
handleBroughtToFront();
|
|
|
|
return true;
|
|
}
|
|
|
|
void toFront (bool makeActive) override
|
|
{
|
|
setMinimised (false);
|
|
|
|
const bool oldDeactivate = shouldDeactivateTitleBar;
|
|
shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
|
|
|
|
callFunctionIfNotLocked (makeActive ? &toFrontCallback1 : &toFrontCallback2, hwnd);
|
|
|
|
shouldDeactivateTitleBar = oldDeactivate;
|
|
|
|
if (! makeActive)
|
|
{
|
|
// in this case a broughttofront call won't have occured, so do it now..
|
|
handleBroughtToFront();
|
|
}
|
|
}
|
|
|
|
void toBehind (ComponentPeer* other) override
|
|
{
|
|
if (HWNDComponentPeer* const otherPeer = dynamic_cast<HWNDComponentPeer*> (other))
|
|
{
|
|
setMinimised (false);
|
|
|
|
// Must be careful not to try to put a topmost window behind a normal one, or Windows
|
|
// promotes the normal one to be topmost!
|
|
if (component.isAlwaysOnTop() == otherPeer->getComponent().isAlwaysOnTop())
|
|
setWindowZOrder (hwnd, otherPeer->hwnd);
|
|
else if (otherPeer->getComponent().isAlwaysOnTop())
|
|
setWindowZOrder (hwnd, HWND_TOP);
|
|
}
|
|
else
|
|
{
|
|
jassertfalse; // wrong type of window?
|
|
}
|
|
}
|
|
|
|
bool isFocused() const override
|
|
{
|
|
return callFunctionIfNotLocked (&getFocusCallback, 0) == (void*) hwnd;
|
|
}
|
|
|
|
void grabFocus() override
|
|
{
|
|
const bool oldDeactivate = shouldDeactivateTitleBar;
|
|
shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
|
|
|
|
callFunctionIfNotLocked (&setFocusCallback, hwnd);
|
|
|
|
shouldDeactivateTitleBar = oldDeactivate;
|
|
}
|
|
|
|
void textInputRequired (Point<int>, TextInputTarget&) override
|
|
{
|
|
if (! hasCreatedCaret)
|
|
{
|
|
hasCreatedCaret = true;
|
|
CreateCaret (hwnd, (HBITMAP) 1, 0, 0);
|
|
}
|
|
|
|
ShowCaret (hwnd);
|
|
SetCaretPos (0, 0);
|
|
}
|
|
|
|
void dismissPendingTextInput() override
|
|
{
|
|
imeHandler.handleSetContext (hwnd, false);
|
|
}
|
|
|
|
void repaint (const Rectangle<int>& area) override
|
|
{
|
|
const RECT r = { area.getX(), area.getY(), area.getRight(), area.getBottom() };
|
|
InvalidateRect (hwnd, &r, FALSE);
|
|
}
|
|
|
|
void performAnyPendingRepaintsNow() override
|
|
{
|
|
if (component.isVisible())
|
|
{
|
|
WeakReference<Component> localRef (&component);
|
|
MSG m;
|
|
|
|
if (isUsingUpdateLayeredWindow() || PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE))
|
|
if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp)
|
|
handlePaintMessage();
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
static HWNDComponentPeer* getOwnerOfWindow (HWND h) noexcept
|
|
{
|
|
if (h != 0 && JuceWindowIdentifier::isJUCEWindow (h))
|
|
return (HWNDComponentPeer*) GetWindowLongPtr (h, 8);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//==============================================================================
|
|
bool isInside (HWND h) const noexcept
|
|
{
|
|
return GetAncestor (hwnd, GA_ROOT) == h;
|
|
}
|
|
|
|
//==============================================================================
|
|
static bool isKeyDown (const int key) noexcept { return (GetAsyncKeyState (key) & 0x8000) != 0; }
|
|
|
|
static void updateKeyModifiers() noexcept
|
|
{
|
|
int keyMods = 0;
|
|
if (isKeyDown (VK_SHIFT)) keyMods |= ModifierKeys::shiftModifier;
|
|
if (isKeyDown (VK_CONTROL)) keyMods |= ModifierKeys::ctrlModifier;
|
|
if (isKeyDown (VK_MENU)) keyMods |= ModifierKeys::altModifier;
|
|
if (isKeyDown (VK_RMENU)) keyMods &= ~(ModifierKeys::ctrlModifier | ModifierKeys::altModifier);
|
|
|
|
currentModifiers = currentModifiers.withOnlyMouseButtons().withFlags (keyMods);
|
|
}
|
|
|
|
static void updateModifiersFromWParam (const WPARAM wParam)
|
|
{
|
|
int mouseMods = 0;
|
|
if (wParam & MK_LBUTTON) mouseMods |= ModifierKeys::leftButtonModifier;
|
|
if (wParam & MK_RBUTTON) mouseMods |= ModifierKeys::rightButtonModifier;
|
|
if (wParam & MK_MBUTTON) mouseMods |= ModifierKeys::middleButtonModifier;
|
|
|
|
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (mouseMods);
|
|
updateKeyModifiers();
|
|
}
|
|
|
|
//==============================================================================
|
|
bool dontRepaint;
|
|
|
|
static ModifierKeys currentModifiers;
|
|
static ModifierKeys modifiersAtLastCallback;
|
|
|
|
//==============================================================================
|
|
class JuceDropTarget : public ComBaseClassHelper<IDropTarget>
|
|
{
|
|
public:
|
|
JuceDropTarget (HWNDComponentPeer& p) : ownerInfo (new OwnerInfo (p)) {}
|
|
|
|
void clear()
|
|
{
|
|
ownerInfo = nullptr;
|
|
}
|
|
|
|
JUCE_COMRESULT DragEnter (IDataObject* pDataObject, DWORD grfKeyState, POINTL mousePos, DWORD* pdwEffect)
|
|
{
|
|
HRESULT hr = updateFileList (pDataObject);
|
|
if (FAILED (hr))
|
|
return hr;
|
|
|
|
return DragOver (grfKeyState, mousePos, pdwEffect);
|
|
}
|
|
|
|
JUCE_COMRESULT DragLeave()
|
|
{
|
|
if (ownerInfo == nullptr)
|
|
return S_FALSE;
|
|
|
|
ownerInfo->owner.handleDragExit (ownerInfo->dragInfo);
|
|
return S_OK;
|
|
}
|
|
|
|
JUCE_COMRESULT DragOver (DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect)
|
|
{
|
|
if (ownerInfo == nullptr)
|
|
return S_FALSE;
|
|
|
|
ownerInfo->dragInfo.position = ownerInfo->getMousePos (mousePos).roundToInt();
|
|
const bool wasWanted = ownerInfo->owner.handleDragMove (ownerInfo->dragInfo);
|
|
*pdwEffect = wasWanted ? (DWORD) DROPEFFECT_COPY : (DWORD) DROPEFFECT_NONE;
|
|
return S_OK;
|
|
}
|
|
|
|
JUCE_COMRESULT Drop (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect)
|
|
{
|
|
HRESULT hr = updateFileList (pDataObject);
|
|
if (SUCCEEDED (hr))
|
|
{
|
|
ownerInfo->dragInfo.position = ownerInfo->getMousePos (mousePos).roundToInt();
|
|
const bool wasWanted = ownerInfo->owner.handleDragDrop (ownerInfo->dragInfo);
|
|
*pdwEffect = wasWanted ? (DWORD) DROPEFFECT_COPY : (DWORD) DROPEFFECT_NONE;
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
private:
|
|
struct OwnerInfo
|
|
{
|
|
OwnerInfo (HWNDComponentPeer& p) : owner (p) {}
|
|
|
|
Point<float> getMousePos (const POINTL& mousePos) const
|
|
{
|
|
return owner.globalToLocal (Point<float> (static_cast<float> (mousePos.x),
|
|
static_cast<float> (mousePos.y)));
|
|
}
|
|
|
|
template <typename CharType>
|
|
void parseFileList (const CharType* names, const SIZE_T totalLen)
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
for (;;)
|
|
{
|
|
unsigned int len = 0;
|
|
while (i + len < totalLen && names [i + len] != 0)
|
|
++len;
|
|
|
|
if (len == 0)
|
|
break;
|
|
|
|
dragInfo.files.add (String (names + i, len));
|
|
i += len + 1;
|
|
}
|
|
}
|
|
|
|
HWNDComponentPeer& owner;
|
|
ComponentPeer::DragInfo dragInfo;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (OwnerInfo)
|
|
};
|
|
|
|
ScopedPointer<OwnerInfo> ownerInfo;
|
|
|
|
struct DroppedData
|
|
{
|
|
DroppedData (IDataObject* const dataObject, const CLIPFORMAT type)
|
|
: data (nullptr)
|
|
{
|
|
FORMATETC format = { type, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
STGMEDIUM resetMedium = { TYMED_HGLOBAL, { 0 }, 0 };
|
|
medium = resetMedium;
|
|
|
|
if (SUCCEEDED (error = dataObject->GetData (&format, &medium)))
|
|
{
|
|
dataSize = GlobalSize (medium.hGlobal);
|
|
data = GlobalLock (medium.hGlobal);
|
|
}
|
|
}
|
|
|
|
~DroppedData()
|
|
{
|
|
if (data != nullptr)
|
|
GlobalUnlock (medium.hGlobal);
|
|
}
|
|
|
|
HRESULT error;
|
|
STGMEDIUM medium;
|
|
void* data;
|
|
SIZE_T dataSize;
|
|
};
|
|
|
|
HRESULT updateFileList (IDataObject* const dataObject)
|
|
{
|
|
if (ownerInfo == nullptr)
|
|
return S_FALSE;
|
|
|
|
ownerInfo->dragInfo.clear();
|
|
|
|
DroppedData textData (dataObject, CF_UNICODETEXT);
|
|
|
|
if (SUCCEEDED (textData.error))
|
|
{
|
|
ownerInfo->dragInfo.text = String (CharPointer_UTF16 ((const WCHAR*) textData.data),
|
|
CharPointer_UTF16 ((const WCHAR*) addBytesToPointer (textData.data, textData.dataSize)));
|
|
}
|
|
else
|
|
{
|
|
DroppedData fileData (dataObject, CF_HDROP);
|
|
|
|
if (SUCCEEDED (fileData.error))
|
|
{
|
|
const LPDROPFILES dropFiles = static_cast<const LPDROPFILES> (fileData.data);
|
|
const void* const names = addBytesToPointer (dropFiles, sizeof (DROPFILES));
|
|
|
|
if (dropFiles->fWide)
|
|
ownerInfo->parseFileList (static_cast<const WCHAR*> (names), fileData.dataSize);
|
|
else
|
|
ownerInfo->parseFileList (static_cast<const char*> (names), fileData.dataSize);
|
|
}
|
|
else
|
|
{
|
|
return fileData.error;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (JuceDropTarget)
|
|
};
|
|
|
|
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
|
|
static bool offerKeyMessageToJUCEWindow (MSG& m)
|
|
{
|
|
if (m.message == WM_KEYDOWN || m.message == WM_KEYUP)
|
|
if (Component::getCurrentlyFocusedComponent() != nullptr)
|
|
if (HWNDComponentPeer* h = getOwnerOfWindow (m.hwnd))
|
|
if (m.message == WM_KEYDOWN ? h->doKeyDown (m.wParam)
|
|
: h->doKeyUp (m.wParam))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
HWND hwnd, parentToAddTo;
|
|
ScopedPointer<DropShadower> shadower;
|
|
RenderingEngineType currentRenderingEngine;
|
|
#if JUCE_DIRECT2D
|
|
ScopedPointer<Direct2DLowLevelGraphicsContext> direct2DContext;
|
|
#endif
|
|
uint32 lastPaintTime;
|
|
ULONGLONG lastMagnifySize;
|
|
bool fullScreen, isDragging, isMouseOver, hasCreatedCaret, constrainerIsResizing;
|
|
BorderSize<int> windowBorder;
|
|
HICON currentWindowIcon;
|
|
JuceDropTarget* dropTarget;
|
|
uint8 updateLayeredWindowAlpha;
|
|
MultiTouchMapper<DWORD> currentTouches;
|
|
|
|
//==============================================================================
|
|
class TemporaryImage : public Timer
|
|
{
|
|
public:
|
|
TemporaryImage() {}
|
|
|
|
Image& getImage (const bool transparent, const int w, const int h)
|
|
{
|
|
const Image::PixelFormat format = transparent ? Image::ARGB : Image::RGB;
|
|
|
|
if ((! image.isValid()) || image.getWidth() < w || image.getHeight() < h || image.getFormat() != format)
|
|
image = Image (new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false));
|
|
|
|
startTimer (3000);
|
|
return image;
|
|
}
|
|
|
|
void timerCallback() override
|
|
{
|
|
stopTimer();
|
|
image = Image::null;
|
|
}
|
|
|
|
private:
|
|
Image image;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryImage)
|
|
};
|
|
|
|
TemporaryImage offscreenImageGenerator;
|
|
|
|
//==============================================================================
|
|
class WindowClassHolder : private DeletedAtShutdown
|
|
{
|
|
public:
|
|
WindowClassHolder()
|
|
{
|
|
// this name has to be different for each app/dll instance because otherwise poor old Windows can
|
|
// get a bit confused (even despite it not being a process-global window class).
|
|
String windowClassName ("JUCE_");
|
|
windowClassName << String::toHexString (Time::currentTimeMillis());
|
|
|
|
HINSTANCE moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
|
|
|
|
TCHAR moduleFile [1024] = { 0 };
|
|
GetModuleFileName (moduleHandle, moduleFile, 1024);
|
|
WORD iconNum = 0;
|
|
|
|
WNDCLASSEX wcex = { 0 };
|
|
wcex.cbSize = sizeof (wcex);
|
|
wcex.style = CS_OWNDC;
|
|
wcex.lpfnWndProc = (WNDPROC) windowProc;
|
|
wcex.lpszClassName = windowClassName.toWideCharPointer();
|
|
wcex.cbWndExtra = 32;
|
|
wcex.hInstance = moduleHandle;
|
|
wcex.hIcon = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
|
|
iconNum = 1;
|
|
wcex.hIconSm = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
|
|
|
|
atom = RegisterClassEx (&wcex);
|
|
jassert (atom != 0);
|
|
|
|
isEventBlockedByModalComps = checkEventBlockedByModalComps;
|
|
}
|
|
|
|
~WindowClassHolder()
|
|
{
|
|
if (ComponentPeer::getNumPeers() == 0)
|
|
UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
|
|
|
|
clearSingletonInstance();
|
|
}
|
|
|
|
LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) atom; }
|
|
|
|
juce_DeclareSingleton_SingleThreaded_Minimal (WindowClassHolder)
|
|
|
|
private:
|
|
ATOM atom;
|
|
|
|
static bool isHWNDBlockedByModalComponents (HWND h)
|
|
{
|
|
for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
|
|
if (Component* const c = Desktop::getInstance().getComponent (i))
|
|
if ((! c->isCurrentlyBlockedByAnotherModalComponent())
|
|
&& IsChild ((HWND) c->getWindowHandle(), h))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool checkEventBlockedByModalComps (const MSG& m)
|
|
{
|
|
if (Component::getNumCurrentlyModalComponents() == 0 || JuceWindowIdentifier::isJUCEWindow (m.hwnd))
|
|
return false;
|
|
|
|
switch (m.message)
|
|
{
|
|
case WM_MOUSEMOVE:
|
|
case WM_NCMOUSEMOVE:
|
|
case 0x020A: /* WM_MOUSEWHEEL */
|
|
case 0x020E: /* WM_MOUSEHWHEEL */
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYUP:
|
|
case WM_CHAR:
|
|
case WM_APPCOMMAND:
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_MOUSEACTIVATE:
|
|
case WM_NCMOUSEHOVER:
|
|
case WM_MOUSEHOVER:
|
|
case WM_TOUCH:
|
|
return isHWNDBlockedByModalComponents (m.hwnd);
|
|
|
|
case WM_NCLBUTTONDOWN:
|
|
case WM_NCLBUTTONDBLCLK:
|
|
case WM_NCRBUTTONDOWN:
|
|
case WM_NCRBUTTONDBLCLK:
|
|
case WM_NCMBUTTONDOWN:
|
|
case WM_NCMBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONDBLCLK:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_RBUTTONDBLCLK:
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
if (isHWNDBlockedByModalComponents (m.hwnd))
|
|
{
|
|
if (Component* const modal = Component::getCurrentlyModalComponent (0))
|
|
modal->inputAttemptWhenModal();
|
|
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (WindowClassHolder)
|
|
};
|
|
|
|
//==============================================================================
|
|
static void* createWindowCallback (void* userData)
|
|
{
|
|
static_cast<HWNDComponentPeer*> (userData)->createWindow();
|
|
return nullptr;
|
|
}
|
|
|
|
void createWindow()
|
|
{
|
|
DWORD exstyle = 0;
|
|
DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
|
|
|
|
if (hasTitleBar())
|
|
{
|
|
type |= WS_OVERLAPPED;
|
|
|
|
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 if (parentToAddTo != 0)
|
|
{
|
|
type |= WS_CHILD;
|
|
}
|
|
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()->getWindowClassName(),
|
|
L"", type, 0, 0, 0, 0, parentToAddTo, 0,
|
|
(HINSTANCE) Process::getCurrentModuleInstanceHandle(), 0);
|
|
|
|
if (hwnd != 0)
|
|
{
|
|
SetWindowLongPtr (hwnd, 0, 0);
|
|
SetWindowLongPtr (hwnd, 8, (LONG_PTR) this);
|
|
JuceWindowIdentifier::setAsJUCEWindow (hwnd, true);
|
|
|
|
if (dropTarget == nullptr)
|
|
{
|
|
HWNDComponentPeer* peer = nullptr;
|
|
|
|
if (dontRepaint)
|
|
peer = getOwnerOfWindow (parentToAddTo);
|
|
|
|
if (peer == nullptr)
|
|
peer = this;
|
|
|
|
dropTarget = new JuceDropTarget (*peer);
|
|
}
|
|
|
|
RegisterDragDrop (hwnd, dropTarget);
|
|
|
|
if (canUseMultiTouch())
|
|
registerTouchWindow (hwnd, 0);
|
|
|
|
setDPIAwareness();
|
|
setMessageFilter();
|
|
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);
|
|
|
|
const float alpha = component.getAlpha();
|
|
if (alpha < 1.0f)
|
|
setAlpha (alpha);
|
|
}
|
|
else
|
|
{
|
|
jassertfalse;
|
|
}
|
|
}
|
|
|
|
static void* destroyWindowCallback (void* handle)
|
|
{
|
|
RevokeDragDrop ((HWND) handle);
|
|
DestroyWindow ((HWND) handle);
|
|
return nullptr;
|
|
}
|
|
|
|
static void* toFrontCallback1 (void* h)
|
|
{
|
|
SetForegroundWindow ((HWND) h);
|
|
return nullptr;
|
|
}
|
|
|
|
static void* toFrontCallback2 (void* h)
|
|
{
|
|
setWindowZOrder ((HWND) h, HWND_TOP);
|
|
return nullptr;
|
|
}
|
|
|
|
static void* setFocusCallback (void* h)
|
|
{
|
|
SetFocus ((HWND) h);
|
|
return nullptr;
|
|
}
|
|
|
|
static void* getFocusCallback (void*)
|
|
{
|
|
return GetFocus();
|
|
}
|
|
|
|
bool isUsingUpdateLayeredWindow() const
|
|
{
|
|
return ! component.isOpaque();
|
|
}
|
|
|
|
bool hasTitleBar() const noexcept { return (styleFlags & windowHasTitleBar) != 0; }
|
|
|
|
|
|
void setIcon (const Image& newIcon)
|
|
{
|
|
HICON hicon = IconConverters::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 setMessageFilter()
|
|
{
|
|
typedef BOOL (WINAPI* ChangeWindowMessageFilterExFunc) (HWND, UINT, DWORD, PVOID);
|
|
|
|
if (ChangeWindowMessageFilterExFunc changeMessageFilter
|
|
= (ChangeWindowMessageFilterExFunc) getUser32Function ("ChangeWindowMessageFilterEx"))
|
|
{
|
|
changeMessageFilter (hwnd, WM_DROPFILES, 1 /*MSGFLT_ALLOW*/, nullptr);
|
|
changeMessageFilter (hwnd, WM_COPYDATA, 1 /*MSGFLT_ALLOW*/, nullptr);
|
|
changeMessageFilter (hwnd, 0x49, 1 /*MSGFLT_ALLOW*/, nullptr);
|
|
}
|
|
}
|
|
|
|
struct ChildWindowClippingInfo
|
|
{
|
|
HDC dc;
|
|
HWNDComponentPeer* peer;
|
|
RectangleList<int>* clip;
|
|
Point<int> origin;
|
|
int savedDC;
|
|
};
|
|
|
|
static BOOL CALLBACK clipChildWindowCallback (HWND hwnd, LPARAM context)
|
|
{
|
|
if (IsWindowVisible (hwnd))
|
|
{
|
|
ChildWindowClippingInfo& info = *(ChildWindowClippingInfo*) context;
|
|
|
|
HWND parent = GetParent (hwnd);
|
|
|
|
if (parent == info.peer->hwnd)
|
|
{
|
|
RECT r = getWindowRect (hwnd);
|
|
POINT pos = { r.left, r.top };
|
|
ScreenToClient (GetParent (hwnd), &pos);
|
|
|
|
Rectangle<int> clip (Rectangle<int> (pos.x, pos.y,
|
|
r.right - r.left,
|
|
r.bottom - r.top));
|
|
|
|
info.clip->subtract (clip - info.origin);
|
|
|
|
if (info.savedDC == 0)
|
|
info.savedDC = SaveDC (info.dc);
|
|
|
|
ExcludeClipRect (info.dc, clip.getX(), clip.getY(), clip.getRight(), clip.getBottom());
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//==============================================================================
|
|
void handlePaintMessage()
|
|
{
|
|
#if JUCE_DIRECT2D
|
|
if (direct2DContext != nullptr)
|
|
{
|
|
RECT r;
|
|
|
|
if (GetUpdateRect (hwnd, &r, false))
|
|
{
|
|
direct2DContext->start();
|
|
direct2DContext->clipToRectangle (rectangleFromRECT (r));
|
|
handlePaint (*direct2DContext);
|
|
direct2DContext->end();
|
|
}
|
|
}
|
|
else
|
|
#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)
|
|
{
|
|
const ScopedValueSetter<bool> setter (reentrant, true, false);
|
|
|
|
if (dontRepaint)
|
|
component.handleCommandMessage (0); // (this triggers a repaint in the openGL context)
|
|
else
|
|
performPaint (dc, rgn, regionType, paintStruct);
|
|
}
|
|
|
|
DeleteObject (rgn);
|
|
EndPaint (hwnd, &paintStruct);
|
|
|
|
#ifndef JUCE_GCC
|
|
_fpreset(); // because some graphics cards can unmask FP exceptions
|
|
#endif
|
|
|
|
lastPaintTime = Time::getMillisecondCounter();
|
|
}
|
|
|
|
void performPaint (HDC dc, HRGN rgn, int regionType, PAINTSTRUCT& paintStruct)
|
|
{
|
|
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 = isUsingUpdateLayeredWindow();
|
|
|
|
if (transparent)
|
|
{
|
|
// it's not possible to have a transparent window with a title bar at the moment!
|
|
jassert (! hasTitleBar());
|
|
|
|
RECT r = getWindowRect (hwnd);
|
|
x = y = 0;
|
|
w = r.right - r.left;
|
|
h = r.bottom - r.top;
|
|
}
|
|
|
|
if (w > 0 && h > 0)
|
|
{
|
|
Image& offscreenImage = offscreenImageGenerator.getImage (transparent, w, h);
|
|
|
|
RectangleList<int> contextClip;
|
|
const Rectangle<int> clipBounds (w, h);
|
|
|
|
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));
|
|
|
|
for (int i = (int) ((RGNDATA*) rgnData)->rdh.nCount; --i >= 0;)
|
|
{
|
|
if (rects->right <= x + w && rects->bottom <= y + h)
|
|
{
|
|
const int cx = jmax (x, (int) rects->left);
|
|
contextClip.addWithoutMerging (Rectangle<int> (cx - x, rects->top - y,
|
|
rects->right - cx, rects->bottom - rects->top)
|
|
.getIntersection (clipBounds));
|
|
}
|
|
else
|
|
{
|
|
needToPaintAll = true;
|
|
break;
|
|
}
|
|
|
|
++rects;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needToPaintAll)
|
|
{
|
|
contextClip.clear();
|
|
contextClip.addWithoutMerging (Rectangle<int> (w, h));
|
|
}
|
|
|
|
ChildWindowClippingInfo childClipInfo = { dc, this, &contextClip, Point<int> (x, y), 0 };
|
|
EnumChildWindows (hwnd, clipChildWindowCallback, (LPARAM) &childClipInfo);
|
|
|
|
if (! contextClip.isEmpty())
|
|
{
|
|
if (transparent)
|
|
for (const Rectangle<int>* i = contextClip.begin(), * const e = contextClip.end(); i != e; ++i)
|
|
offscreenImage.clear (*i);
|
|
|
|
// if the component's not opaque, this won't draw properly unless the platform can support this
|
|
jassert (Desktop::canUseSemiTransparentWindows() || component.isOpaque());
|
|
|
|
{
|
|
ScopedPointer<LowLevelGraphicsContext> context (component.getLookAndFeel()
|
|
.createGraphicsContext (offscreenImage, Point<int> (-x, -y), contextClip));
|
|
handlePaint (*context);
|
|
}
|
|
|
|
static_cast<WindowsBitmapImage*> (offscreenImage.getPixelData())
|
|
->blitToWindow (hwnd, dc, transparent, x, y, updateLayeredWindowAlpha);
|
|
}
|
|
|
|
if (childClipInfo.savedDC != 0)
|
|
RestoreDC (dc, childClipInfo.savedDC);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void doMouseEvent (Point<float> position)
|
|
{
|
|
handleMouseEvent (0, position, currentModifiers, getMouseEventTime());
|
|
}
|
|
|
|
StringArray getAvailableRenderingEngines() override
|
|
{
|
|
StringArray s ("Software Renderer");
|
|
|
|
#if JUCE_DIRECT2D
|
|
if (SystemStats::getOperatingSystemType() >= SystemStats::Windows7)
|
|
s.add ("Direct2D");
|
|
#endif
|
|
|
|
return s;
|
|
}
|
|
|
|
int getCurrentRenderingEngine() const override { return currentRenderingEngine; }
|
|
|
|
#if JUCE_DIRECT2D
|
|
void updateDirect2DContext()
|
|
{
|
|
if (currentRenderingEngine != direct2DRenderingEngine)
|
|
direct2DContext = 0;
|
|
else if (direct2DContext == 0)
|
|
direct2DContext = new Direct2DLowLevelGraphicsContext (hwnd);
|
|
}
|
|
#endif
|
|
|
|
void setCurrentRenderingEngine (int index) override
|
|
{
|
|
(void) index;
|
|
|
|
#if JUCE_DIRECT2D
|
|
if (getAvailableRenderingEngines().size() > 1)
|
|
{
|
|
currentRenderingEngine = index == 1 ? direct2DRenderingEngine : softwareRenderingEngine;
|
|
updateDirect2DContext();
|
|
repaint (component.getLocalBounds());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int getMinTimeBetweenMouseMoves()
|
|
{
|
|
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
|
|
return 0;
|
|
|
|
return 1000 / 60; // Throttling the incoming mouse-events seems to still be needed in XP..
|
|
}
|
|
|
|
void doMouseMove (Point<float> position)
|
|
{
|
|
if (! isMouseOver)
|
|
{
|
|
isMouseOver = true;
|
|
ModifierKeys::getCurrentModifiersRealtime(); // (This avoids a rare stuck-button problem when focus is lost unexpectedly)
|
|
updateKeyModifiers();
|
|
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof (tme);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = hwnd;
|
|
tme.dwHoverTime = 0;
|
|
|
|
if (! TrackMouseEvent (&tme))
|
|
jassertfalse;
|
|
|
|
Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
|
|
}
|
|
else if (! isDragging)
|
|
{
|
|
if (! contains (position.roundToInt(), false))
|
|
return;
|
|
}
|
|
|
|
static uint32 lastMouseTime = 0;
|
|
static int minTimeBetweenMouses = getMinTimeBetweenMouseMoves();
|
|
const uint32 now = Time::getMillisecondCounter();
|
|
|
|
if (now >= lastMouseTime + minTimeBetweenMouses)
|
|
{
|
|
lastMouseTime = now;
|
|
doMouseEvent (position);
|
|
}
|
|
}
|
|
|
|
void doMouseDown (Point<float> position, const WPARAM wParam)
|
|
{
|
|
if (GetCapture() != hwnd)
|
|
SetCapture (hwnd);
|
|
|
|
doMouseMove (position);
|
|
|
|
if (isValidPeer (this))
|
|
{
|
|
updateModifiersFromWParam (wParam);
|
|
isDragging = true;
|
|
|
|
doMouseEvent (position);
|
|
}
|
|
}
|
|
|
|
void doMouseUp (Point<float> position, const WPARAM wParam)
|
|
{
|
|
updateModifiersFromWParam (wParam);
|
|
const bool wasDragging = isDragging;
|
|
isDragging = false;
|
|
|
|
// release the mouse capture if the user has released all buttons
|
|
if ((wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) == 0 && hwnd == GetCapture())
|
|
ReleaseCapture();
|
|
|
|
// NB: under some circumstances (e.g. double-clicking a native title bar), a mouse-up can
|
|
// arrive without a mouse-down, so in that case we need to avoid sending a message.
|
|
if (wasDragging)
|
|
doMouseEvent (position);
|
|
}
|
|
|
|
void doCaptureChanged()
|
|
{
|
|
if (constrainerIsResizing)
|
|
{
|
|
if (constrainer != nullptr)
|
|
constrainer->resizeEnd();
|
|
|
|
constrainerIsResizing = false;
|
|
}
|
|
|
|
if (isDragging)
|
|
doMouseUp (getCurrentMousePos(), (WPARAM) 0);
|
|
}
|
|
|
|
void doMouseExit()
|
|
{
|
|
isMouseOver = false;
|
|
doMouseEvent (getCurrentMousePos());
|
|
}
|
|
|
|
ComponentPeer* findPeerUnderMouse (Point<float>& localPos)
|
|
{
|
|
const Point<int> globalPos (getCurrentMousePosGlobal().roundToInt());
|
|
|
|
// Because Windows stupidly sends all wheel events to the window with the keyboard
|
|
// focus, we have to redirect them here according to the mouse pos..
|
|
POINT p = { globalPos.x, globalPos.y };
|
|
HWNDComponentPeer* peer = getOwnerOfWindow (WindowFromPoint (p));
|
|
|
|
if (peer == nullptr)
|
|
peer = this;
|
|
|
|
localPos = peer->globalToLocal (globalPos.toFloat());
|
|
return peer;
|
|
}
|
|
|
|
void doMouseWheel (const WPARAM wParam, const bool isVertical)
|
|
{
|
|
updateKeyModifiers();
|
|
const float amount = jlimit (-1000.0f, 1000.0f, 0.5f * (short) HIWORD (wParam));
|
|
|
|
MouseWheelDetails wheel;
|
|
wheel.deltaX = isVertical ? 0.0f : amount / -256.0f;
|
|
wheel.deltaY = isVertical ? amount / 256.0f : 0.0f;
|
|
wheel.isReversed = false;
|
|
wheel.isSmooth = false;
|
|
wheel.isInertial = false;
|
|
|
|
Point<float> localPos;
|
|
if (ComponentPeer* const peer = findPeerUnderMouse (localPos))
|
|
peer->handleMouseWheel (0, localPos, getMouseEventTime(), wheel);
|
|
}
|
|
|
|
bool doGestureEvent (LPARAM lParam)
|
|
{
|
|
GESTUREINFO gi;
|
|
zerostruct (gi);
|
|
gi.cbSize = sizeof (gi);
|
|
|
|
if (getGestureInfo != nullptr && getGestureInfo ((HGESTUREINFO) lParam, &gi))
|
|
{
|
|
updateKeyModifiers();
|
|
Point<float> localPos;
|
|
|
|
if (ComponentPeer* const peer = findPeerUnderMouse (localPos))
|
|
{
|
|
switch (gi.dwID)
|
|
{
|
|
case 3: /*GID_ZOOM*/
|
|
if (gi.dwFlags != 1 /*GF_BEGIN*/ && lastMagnifySize > 0)
|
|
peer->handleMagnifyGesture (0, localPos, getMouseEventTime(),
|
|
(float) (gi.ullArguments / (double) lastMagnifySize));
|
|
|
|
lastMagnifySize = gi.ullArguments;
|
|
return true;
|
|
|
|
case 4: /*GID_PAN*/
|
|
case 5: /*GID_ROTATE*/
|
|
case 6: /*GID_TWOFINGERTAP*/
|
|
case 7: /*GID_PRESSANDTAP*/
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
LRESULT doTouchEvent (const int numInputs, HTOUCHINPUT eventHandle)
|
|
{
|
|
if ((styleFlags & windowIgnoresMouseClicks) != 0)
|
|
if (HWNDComponentPeer* const parent = getOwnerOfWindow (GetParent (hwnd)))
|
|
if (parent != this)
|
|
return parent->doTouchEvent (numInputs, eventHandle);
|
|
|
|
HeapBlock<TOUCHINPUT> inputInfo (numInputs);
|
|
|
|
if (getTouchInputInfo (eventHandle, numInputs, inputInfo, sizeof (TOUCHINPUT)))
|
|
{
|
|
for (int i = 0; i < numInputs; ++i)
|
|
{
|
|
const DWORD flags = inputInfo[i].dwFlags;
|
|
|
|
if ((flags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE | TOUCHEVENTF_UP)) != 0)
|
|
if (! handleTouchInput (inputInfo[i], (flags & TOUCHEVENTF_PRIMARY) != 0,
|
|
(flags & TOUCHEVENTF_DOWN) != 0, (flags & TOUCHEVENTF_UP) != 0))
|
|
return 0; // abandon method if this window was deleted by the callback
|
|
}
|
|
}
|
|
|
|
closeTouchInputHandle (eventHandle);
|
|
return 0;
|
|
}
|
|
|
|
bool handleTouchInput (const TOUCHINPUT& touch, const bool isPrimary, const bool isDown, const bool isUp)
|
|
{
|
|
bool isCancel = false;
|
|
const int touchIndex = currentTouches.getIndexOfTouch (touch.dwID);
|
|
const int64 time = getMouseEventTime();
|
|
const Point<float> pos (globalToLocal (Point<float> (static_cast<float> (TOUCH_COORD_TO_PIXEL (touch.x)),
|
|
static_cast<float> (TOUCH_COORD_TO_PIXEL (touch.y)))));
|
|
ModifierKeys modsToSend (currentModifiers);
|
|
|
|
if (isDown)
|
|
{
|
|
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
|
|
modsToSend = currentModifiers;
|
|
|
|
if (! isPrimary)
|
|
{
|
|
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
|
|
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), time);
|
|
if (! isValidPeer (this)) // (in case this component was deleted by the event)
|
|
return false;
|
|
}
|
|
}
|
|
else if (isUp)
|
|
{
|
|
modsToSend = modsToSend.withoutMouseButtons();
|
|
currentTouches.clearTouch (touchIndex);
|
|
|
|
if (! currentTouches.areAnyTouchesActive())
|
|
isCancel = true;
|
|
}
|
|
else
|
|
{
|
|
modsToSend = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
|
|
}
|
|
|
|
if (isCancel)
|
|
{
|
|
currentTouches.clear();
|
|
currentModifiers = currentModifiers.withoutMouseButtons();
|
|
}
|
|
|
|
if (! isPrimary)
|
|
{
|
|
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, time);
|
|
if (! isValidPeer (this)) // (in case this component was deleted by the event)
|
|
return false;
|
|
}
|
|
|
|
if ((isUp || isCancel) && ! isPrimary)
|
|
{
|
|
handleMouseEvent (touchIndex, Point<float> (-10.0f, -10.0f), currentModifiers, time);
|
|
if (! isValidPeer (this))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//==============================================================================
|
|
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() != nullptr;
|
|
}
|
|
|
|
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;
|
|
|
|
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 ((UINT) key, 2);
|
|
const UINT scanCode = MapVirtualKey ((UINT) key, 0);
|
|
BYTE keyState[256];
|
|
GetKeyboardState (keyState);
|
|
|
|
WCHAR text[16] = { 0 };
|
|
if (ToUnicode ((UINT) key, scanCode, keyState, text, 8, 0) != 1)
|
|
text[0] = 0;
|
|
|
|
used = handleKeyPress ((int) LOWORD (keyChar), (juce_wchar) text[0]) || used;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return used || (Component::getCurrentlyModalComponent() != nullptr);
|
|
}
|
|
|
|
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 ((UINT) 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.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::altModifier))
|
|
textChar = 0;
|
|
}
|
|
|
|
return handleKeyPress (key, textChar);
|
|
}
|
|
|
|
void forwardMessageToParent (UINT message, WPARAM wParam, LPARAM lParam) const
|
|
{
|
|
if (HWND parentH = GetParent (hwnd))
|
|
PostMessage (parentH, message, wParam, lParam);
|
|
}
|
|
|
|
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;
|
|
default: break;
|
|
}
|
|
|
|
if (key != 0)
|
|
{
|
|
updateKeyModifiers();
|
|
|
|
if (hwnd == GetActiveWindow())
|
|
{
|
|
handleKeyPress (key, 0);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isConstrainedNativeWindow() const
|
|
{
|
|
return constrainer != nullptr
|
|
&& (styleFlags & (windowHasTitleBar | windowIsResizable)) == (windowHasTitleBar | windowIsResizable)
|
|
&& ! isKioskMode();
|
|
}
|
|
|
|
Rectangle<int> getCurrentScaledBounds (float scale) const
|
|
{
|
|
return ScalingHelpers::unscaledScreenPosToScaled (scale, windowBorder.addedTo (ScalingHelpers::scaledScreenPosToUnscaled (scale, component.getBounds())));
|
|
}
|
|
|
|
LRESULT handleSizeConstraining (RECT& r, const WPARAM wParam)
|
|
{
|
|
if (isConstrainedNativeWindow())
|
|
{
|
|
const float scale = getComponent().getDesktopScaleFactor();
|
|
Rectangle<int> pos (ScalingHelpers::unscaledScreenPosToScaled (scale, rectangleFromRECT (r)));
|
|
const Rectangle<int> current (getCurrentScaledBounds (scale));
|
|
|
|
constrainer->checkBounds (pos, current,
|
|
Desktop::getInstance().getDisplays().getTotalBounds (true),
|
|
wParam == WMSZ_TOP || wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOPRIGHT,
|
|
wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT,
|
|
wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT,
|
|
wParam == WMSZ_RIGHT || wParam == WMSZ_TOPRIGHT || wParam == WMSZ_BOTTOMRIGHT);
|
|
|
|
pos = ScalingHelpers::scaledScreenPosToUnscaled (scale, pos);
|
|
r.left = pos.getX();
|
|
r.top = pos.getY();
|
|
r.right = pos.getRight();
|
|
r.bottom = pos.getBottom();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT handlePositionChanging (WINDOWPOS& wp)
|
|
{
|
|
if (isConstrainedNativeWindow())
|
|
{
|
|
if ((wp.flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE)
|
|
&& ! Component::isMouseButtonDownAnywhere())
|
|
{
|
|
const float scale = getComponent().getDesktopScaleFactor();
|
|
Rectangle<int> pos (ScalingHelpers::unscaledScreenPosToScaled (scale, Rectangle<int> (wp.x, wp.y, wp.cx, wp.cy)));
|
|
const Rectangle<int> current (getCurrentScaledBounds (scale));
|
|
|
|
constrainer->checkBounds (pos, current,
|
|
Desktop::getInstance().getDisplays().getTotalBounds (true),
|
|
pos.getY() != current.getY() && pos.getBottom() == current.getBottom(),
|
|
pos.getX() != current.getX() && pos.getRight() == current.getRight(),
|
|
pos.getY() == current.getY() && pos.getBottom() != current.getBottom(),
|
|
pos.getX() == current.getX() && pos.getRight() != current.getRight());
|
|
|
|
pos = ScalingHelpers::scaledScreenPosToUnscaled (scale, pos);
|
|
wp.x = pos.getX();
|
|
wp.y = pos.getY();
|
|
wp.cx = pos.getWidth();
|
|
wp.cy = pos.getHeight();
|
|
}
|
|
}
|
|
|
|
if (((wp.flags & SWP_SHOWWINDOW) != 0 && ! component.isVisible()))
|
|
component.setVisible (true);
|
|
else if (((wp.flags & SWP_HIDEWINDOW) != 0 && component.isVisible()))
|
|
component.setVisible (false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool handlePositionChanged()
|
|
{
|
|
const Point<float> pos (getCurrentMousePos());
|
|
|
|
if (contains (pos.roundToInt(), false))
|
|
{
|
|
doMouseEvent (pos);
|
|
|
|
if (! isValidPeer (this))
|
|
return true;
|
|
}
|
|
|
|
handleMovedOrResized();
|
|
return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly..
|
|
}
|
|
|
|
void handleAppActivation (const WPARAM wParam)
|
|
{
|
|
modifiersAtLastCallback = -1;
|
|
updateKeyModifiers();
|
|
|
|
if (isMinimised())
|
|
{
|
|
component.repaint();
|
|
handleMovedOrResized();
|
|
|
|
if (! isValidPeer (this))
|
|
return;
|
|
}
|
|
|
|
Component* underMouse = component.getComponentAt (component.getMouseXYRelative());
|
|
|
|
if (underMouse == nullptr)
|
|
underMouse = &component;
|
|
|
|
if (underMouse->isCurrentlyBlockedByAnotherModalComponent())
|
|
{
|
|
if (LOWORD (wParam) == WA_CLICKACTIVE)
|
|
Component::getCurrentlyModalComponent()->inputAttemptWhenModal();
|
|
else
|
|
ModalComponentManager::getInstance()->bringModalComponentsToFront();
|
|
}
|
|
else
|
|
{
|
|
handleBroughtToFront();
|
|
}
|
|
}
|
|
|
|
void handlePowerBroadcast (WPARAM wParam)
|
|
{
|
|
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case PBT_APMSUSPEND: app->suspended(); break;
|
|
|
|
case PBT_APMQUERYSUSPENDFAILED:
|
|
case PBT_APMRESUMECRITICAL:
|
|
case PBT_APMRESUMESUSPEND:
|
|
case PBT_APMRESUMEAUTOMATIC: app->resumed(); break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleLeftClickInNCArea (WPARAM wParam)
|
|
{
|
|
if (! sendInputAttemptWhenModalMessage())
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case HTBOTTOM:
|
|
case HTBOTTOMLEFT:
|
|
case HTBOTTOMRIGHT:
|
|
case HTGROWBOX:
|
|
case HTLEFT:
|
|
case HTRIGHT:
|
|
case HTTOP:
|
|
case HTTOPLEFT:
|
|
case HTTOPRIGHT:
|
|
if (isConstrainedNativeWindow())
|
|
{
|
|
constrainerIsResizing = true;
|
|
constrainer->resizeStart();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void initialiseSysMenu (HMENU menu) const
|
|
{
|
|
if (! hasTitleBar())
|
|
{
|
|
if (isFullScreen())
|
|
{
|
|
EnableMenuItem (menu, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
|
|
EnableMenuItem (menu, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
|
|
}
|
|
else if (! isMinimised())
|
|
{
|
|
EnableMenuItem (menu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
|
|
}
|
|
}
|
|
}
|
|
|
|
void doSettingChange()
|
|
{
|
|
Desktop& desktop = Desktop::getInstance();
|
|
|
|
const_cast<Desktop::Displays&> (desktop.getDisplays()).refresh();
|
|
|
|
if (fullScreen && ! isMinimised())
|
|
{
|
|
const Desktop::Displays::Display& display
|
|
= desktop.getDisplays().getDisplayContaining (component.getScreenBounds().getCentre());
|
|
|
|
setWindowPos (hwnd, display.userArea * display.scale,
|
|
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING);
|
|
}
|
|
}
|
|
|
|
void handleDPIChange() // happens when a window moves to a screen with a different DPI.
|
|
{
|
|
}
|
|
|
|
//==============================================================================
|
|
public:
|
|
static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (HWNDComponentPeer* const peer = getOwnerOfWindow (h))
|
|
{
|
|
jassert (isValidPeer (peer));
|
|
return peer->peerWindowProc (h, message, wParam, lParam);
|
|
}
|
|
|
|
return DefWindowProcW (h, message, wParam, lParam);
|
|
}
|
|
|
|
private:
|
|
static void* callFunctionIfNotLocked (MessageCallbackFunction* callback, void* userData)
|
|
{
|
|
if (MessageManager::getInstance()->currentThreadHasLockedMessageManager())
|
|
return callback (userData);
|
|
|
|
return MessageManager::getInstance()->callFunctionOnMessageThread (callback, userData);
|
|
}
|
|
|
|
static Point<float> getPointFromLParam (LPARAM lParam) noexcept
|
|
{
|
|
return Point<float> (static_cast<float> (GET_X_LPARAM (lParam)),
|
|
static_cast<float> (GET_Y_LPARAM (lParam)));
|
|
}
|
|
|
|
static Point<float> getCurrentMousePosGlobal() noexcept
|
|
{
|
|
return getPointFromLParam (GetMessagePos());
|
|
}
|
|
|
|
Point<float> getCurrentMousePos() noexcept
|
|
{
|
|
return globalToLocal (getCurrentMousePosGlobal());
|
|
}
|
|
|
|
LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
//==============================================================================
|
|
case WM_NCHITTEST:
|
|
if ((styleFlags & windowIgnoresMouseClicks) != 0)
|
|
return HTTRANSPARENT;
|
|
|
|
if (! hasTitleBar())
|
|
return HTCLIENT;
|
|
|
|
break;
|
|
|
|
//==============================================================================
|
|
case WM_PAINT:
|
|
handlePaintMessage();
|
|
return 0;
|
|
|
|
case WM_NCPAINT:
|
|
if (wParam != 1) // (1 = a repaint of the entire NC region)
|
|
handlePaintMessage(); // this must be done, even with native titlebars, or there are rendering artifacts.
|
|
|
|
if (hasTitleBar())
|
|
break; // let the DefWindowProc handle drawing the frame.
|
|
|
|
return 0;
|
|
|
|
case WM_ERASEBKGND:
|
|
case WM_NCCALCSIZE:
|
|
if (hasTitleBar())
|
|
break;
|
|
|
|
return 1;
|
|
|
|
//==============================================================================
|
|
case WM_MOUSEMOVE: doMouseMove (getPointFromLParam (lParam)); return 0;
|
|
case WM_MOUSELEAVE: doMouseExit(); return 0;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN: doMouseDown (getPointFromLParam (lParam), wParam); return 0;
|
|
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP: doMouseUp (getPointFromLParam (lParam), wParam); return 0;
|
|
|
|
case 0x020A: /* WM_MOUSEWHEEL */ doMouseWheel (wParam, true); return 0;
|
|
case 0x020E: /* WM_MOUSEHWHEEL */ doMouseWheel (wParam, false); return 0;
|
|
|
|
case WM_CAPTURECHANGED: doCaptureChanged(); return 0;
|
|
|
|
case WM_NCMOUSEMOVE:
|
|
if (hasTitleBar())
|
|
break;
|
|
|
|
return 0;
|
|
|
|
case WM_TOUCH:
|
|
if (getTouchInputInfo != nullptr)
|
|
return doTouchEvent ((int) wParam, (HTOUCHINPUT) lParam);
|
|
|
|
break;
|
|
|
|
case 0x119: /* WM_GESTURE */
|
|
if (doGestureEvent (lParam))
|
|
return 0;
|
|
|
|
break;
|
|
|
|
//==============================================================================
|
|
case WM_SIZING: return handleSizeConstraining (*(RECT*) lParam, wParam);
|
|
case WM_WINDOWPOSCHANGING: return handlePositionChanging (*(WINDOWPOS*) lParam);
|
|
|
|
case WM_WINDOWPOSCHANGED:
|
|
if (handlePositionChanged())
|
|
return 0;
|
|
|
|
break;
|
|
|
|
//==============================================================================
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
if (doKeyDown (wParam))
|
|
return 0;
|
|
|
|
forwardMessageToParent (message, wParam, lParam);
|
|
break;
|
|
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYUP:
|
|
if (doKeyUp (wParam))
|
|
return 0;
|
|
|
|
forwardMessageToParent (message, wParam, lParam);
|
|
break;
|
|
|
|
case WM_CHAR:
|
|
if (doKeyChar ((int) wParam, lParam))
|
|
return 0;
|
|
|
|
forwardMessageToParent (message, wParam, lParam);
|
|
break;
|
|
|
|
case WM_APPCOMMAND:
|
|
if (doAppCommand (lParam))
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
case WM_MENUCHAR: // triggered when alt+something is pressed
|
|
return MNC_CLOSE << 16; // (avoids making the default system beep)
|
|
|
|
//==============================================================================
|
|
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 (nullptr); // 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)
|
|
{
|
|
handleAppActivation (wParam);
|
|
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)
|
|
{
|
|
component.setVisible (true);
|
|
handleBroughtToFront();
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
if (! component.isCurrentlyBlockedByAnotherModalComponent())
|
|
handleUserClosingWindow();
|
|
|
|
return 0;
|
|
|
|
case WM_QUERYENDSESSION:
|
|
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
|
|
{
|
|
app->systemRequestedQuit();
|
|
return MessageManager::getInstance()->hasStopMessageBeenSent();
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_POWERBROADCAST:
|
|
handlePowerBroadcast (wParam);
|
|
break;
|
|
|
|
case WM_SYNCPAINT:
|
|
return 0;
|
|
|
|
case WM_DISPLAYCHANGE:
|
|
InvalidateRect (h, 0, 0);
|
|
// intentional fall-through...
|
|
case WM_SETTINGCHANGE: // note the fall-through in the previous case!
|
|
doSettingChange();
|
|
break;
|
|
|
|
case 0x2e0: // WM_DPICHANGED
|
|
handleDPIChange();
|
|
break;
|
|
|
|
case WM_INITMENU:
|
|
initialiseSysMenu ((HMENU) wParam);
|
|
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())
|
|
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:
|
|
handleLeftClickInNCArea (wParam);
|
|
break;
|
|
|
|
case WM_NCRBUTTONDOWN:
|
|
case WM_NCMBUTTONDOWN:
|
|
sendInputAttemptWhenModalMessage();
|
|
break;
|
|
|
|
case WM_IME_SETCONTEXT:
|
|
imeHandler.handleSetContext (h, wParam == TRUE);
|
|
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
|
|
break;
|
|
|
|
case WM_IME_STARTCOMPOSITION: imeHandler.handleStartComposition (*this); return 0;
|
|
case WM_IME_ENDCOMPOSITION: imeHandler.handleEndComposition (*this, h); break;
|
|
case WM_IME_COMPOSITION: imeHandler.handleComposition (*this, h, lParam); return 0;
|
|
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTALLKEYS;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DefWindowProcW (h, message, wParam, lParam);
|
|
}
|
|
|
|
bool sendInputAttemptWhenModalMessage()
|
|
{
|
|
if (component.isCurrentlyBlockedByAnotherModalComponent())
|
|
{
|
|
if (Component* const current = Component::getCurrentlyModalComponent())
|
|
current->inputAttemptWhenModal();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//==============================================================================
|
|
class IMEHandler
|
|
{
|
|
public:
|
|
IMEHandler()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void handleSetContext (HWND hWnd, const bool windowIsActive)
|
|
{
|
|
if (compositionInProgress && ! windowIsActive)
|
|
{
|
|
compositionInProgress = false;
|
|
|
|
if (HIMC hImc = ImmGetContext (hWnd))
|
|
{
|
|
ImmNotifyIME (hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
|
ImmReleaseContext (hWnd, hImc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleStartComposition (ComponentPeer& owner)
|
|
{
|
|
reset();
|
|
|
|
if (TextInputTarget* const target = owner.findCurrentTextInputTarget())
|
|
target->insertTextAtCaret (String::empty);
|
|
}
|
|
|
|
void handleEndComposition (ComponentPeer& owner, HWND hWnd)
|
|
{
|
|
if (compositionInProgress)
|
|
{
|
|
// If this occurs, the user has cancelled the composition, so clear their changes..
|
|
if (TextInputTarget* const target = owner.findCurrentTextInputTarget())
|
|
{
|
|
target->setHighlightedRegion (compositionRange);
|
|
target->insertTextAtCaret (String::empty);
|
|
compositionRange.setLength (0);
|
|
|
|
target->setHighlightedRegion (Range<int>::emptyRange (compositionRange.getEnd()));
|
|
target->setTemporaryUnderlining (Array<Range<int> >());
|
|
}
|
|
|
|
if (HIMC hImc = ImmGetContext (hWnd))
|
|
{
|
|
ImmNotifyIME (hImc, NI_CLOSECANDIDATE, 0, 0);
|
|
ImmReleaseContext (hWnd, hImc);
|
|
}
|
|
}
|
|
|
|
reset();
|
|
}
|
|
|
|
void handleComposition (ComponentPeer& owner, HWND hWnd, const LPARAM lParam)
|
|
{
|
|
TextInputTarget* const target = owner.findCurrentTextInputTarget();
|
|
HIMC hImc = ImmGetContext (hWnd);
|
|
|
|
if (target == nullptr || hImc == 0)
|
|
return;
|
|
|
|
if (compositionRange.getStart() < 0)
|
|
compositionRange = Range<int>::emptyRange (target->getHighlightedRegion().getStart());
|
|
|
|
if ((lParam & GCS_RESULTSTR) != 0) // (composition has finished)
|
|
{
|
|
replaceCurrentSelection (target, getCompositionString (hImc, GCS_RESULTSTR),
|
|
Range<int>::emptyRange (-1));
|
|
|
|
reset();
|
|
target->setTemporaryUnderlining (Array<Range<int> >());
|
|
}
|
|
else if ((lParam & GCS_COMPSTR) != 0) // (composition is still in-progress)
|
|
{
|
|
replaceCurrentSelection (target, getCompositionString (hImc, GCS_COMPSTR),
|
|
getCompositionSelection (hImc, lParam));
|
|
|
|
target->setTemporaryUnderlining (getCompositionUnderlines (hImc, lParam));
|
|
compositionInProgress = true;
|
|
}
|
|
|
|
moveCandidateWindowToLeftAlignWithSelection (hImc, owner, target);
|
|
ImmReleaseContext (hWnd, hImc);
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
Range<int> compositionRange; // The range being modified in the TextInputTarget
|
|
bool compositionInProgress;
|
|
|
|
//==============================================================================
|
|
void reset()
|
|
{
|
|
compositionRange = Range<int>::emptyRange (-1);
|
|
compositionInProgress = false;
|
|
}
|
|
|
|
String getCompositionString (HIMC hImc, const DWORD type) const
|
|
{
|
|
jassert (hImc != 0);
|
|
|
|
const int stringSizeBytes = ImmGetCompositionString (hImc, type, 0, 0);
|
|
|
|
if (stringSizeBytes > 0)
|
|
{
|
|
HeapBlock<TCHAR> buffer;
|
|
buffer.calloc (stringSizeBytes / sizeof (TCHAR) + 1);
|
|
ImmGetCompositionString (hImc, type, buffer, (DWORD) stringSizeBytes);
|
|
return String (buffer);
|
|
}
|
|
|
|
return String::empty;
|
|
}
|
|
|
|
int getCompositionCaretPos (HIMC hImc, LPARAM lParam, const String& currentIMEString) const
|
|
{
|
|
jassert (hImc != 0);
|
|
|
|
if ((lParam & CS_NOMOVECARET) != 0)
|
|
return compositionRange.getStart();
|
|
|
|
if ((lParam & GCS_CURSORPOS) != 0)
|
|
{
|
|
const int localCaretPos = ImmGetCompositionString (hImc, GCS_CURSORPOS, 0, 0);
|
|
return compositionRange.getStart() + jmax (0, localCaretPos);
|
|
}
|
|
|
|
return compositionRange.getStart() + currentIMEString.length();
|
|
}
|
|
|
|
// Get selected/highlighted range while doing composition:
|
|
// returned range is relative to beginning of TextInputTarget, not composition string
|
|
Range<int> getCompositionSelection (HIMC hImc, LPARAM lParam) const
|
|
{
|
|
jassert (hImc != 0);
|
|
int selectionStart = 0;
|
|
int selectionEnd = 0;
|
|
|
|
if ((lParam & GCS_COMPATTR) != 0)
|
|
{
|
|
// Get size of attributes array:
|
|
const int attributeSizeBytes = ImmGetCompositionString (hImc, GCS_COMPATTR, 0, 0);
|
|
|
|
if (attributeSizeBytes > 0)
|
|
{
|
|
// Get attributes (8 bit flag per character):
|
|
HeapBlock<char> attributes ((size_t) attributeSizeBytes);
|
|
ImmGetCompositionString (hImc, GCS_COMPATTR, attributes, (DWORD) attributeSizeBytes);
|
|
|
|
selectionStart = 0;
|
|
|
|
for (selectionStart = 0; selectionStart < attributeSizeBytes; ++selectionStart)
|
|
if (attributes[selectionStart] == ATTR_TARGET_CONVERTED || attributes[selectionStart] == ATTR_TARGET_NOTCONVERTED)
|
|
break;
|
|
|
|
for (selectionEnd = selectionStart; selectionEnd < attributeSizeBytes; ++selectionEnd)
|
|
if (attributes [selectionEnd] != ATTR_TARGET_CONVERTED && attributes[selectionEnd] != ATTR_TARGET_NOTCONVERTED)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Range<int> (selectionStart, selectionEnd) + compositionRange.getStart();
|
|
}
|
|
|
|
void replaceCurrentSelection (TextInputTarget* const target, const String& newContent, Range<int> newSelection)
|
|
{
|
|
if (compositionInProgress)
|
|
target->setHighlightedRegion (compositionRange);
|
|
|
|
target->insertTextAtCaret (newContent);
|
|
compositionRange.setLength (newContent.length());
|
|
|
|
if (newSelection.getStart() < 0)
|
|
newSelection = Range<int>::emptyRange (compositionRange.getEnd());
|
|
|
|
target->setHighlightedRegion (newSelection);
|
|
}
|
|
|
|
Array<Range<int> > getCompositionUnderlines (HIMC hImc, LPARAM lParam) const
|
|
{
|
|
Array<Range<int> > result;
|
|
|
|
if (hImc != 0 && (lParam & GCS_COMPCLAUSE) != 0)
|
|
{
|
|
const int clauseDataSizeBytes = ImmGetCompositionString (hImc, GCS_COMPCLAUSE, 0, 0);
|
|
|
|
if (clauseDataSizeBytes > 0)
|
|
{
|
|
const size_t numItems = clauseDataSizeBytes / sizeof (uint32);
|
|
HeapBlock<uint32> clauseData (numItems);
|
|
|
|
if (ImmGetCompositionString (hImc, GCS_COMPCLAUSE, clauseData, (DWORD) clauseDataSizeBytes) > 0)
|
|
for (size_t i = 0; i + 1 < numItems; ++i)
|
|
result.add (Range<int> ((int) clauseData [i], (int) clauseData [i + 1]) + compositionRange.getStart());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void moveCandidateWindowToLeftAlignWithSelection (HIMC hImc, ComponentPeer& peer, TextInputTarget* target) const
|
|
{
|
|
if (Component* const targetComp = dynamic_cast<Component*> (target))
|
|
{
|
|
const Rectangle<int> area (peer.getComponent().getLocalArea (targetComp, target->getCaretRectangle()));
|
|
|
|
CANDIDATEFORM pos = { 0, CFS_CANDIDATEPOS, { area.getX(), area.getBottom() }, { 0, 0, 0, 0 } };
|
|
ImmSetCandidateWindow (hImc, &pos);
|
|
}
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (IMEHandler)
|
|
};
|
|
|
|
IMEHandler imeHandler;
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentPeer)
|
|
};
|
|
|
|
ModifierKeys HWNDComponentPeer::currentModifiers;
|
|
ModifierKeys HWNDComponentPeer::modifiersAtLastCallback;
|
|
|
|
ComponentPeer* Component::createNewPeer (int styleFlags, void* parentHWND)
|
|
{
|
|
return new HWNDComponentPeer (*this, styleFlags, (HWND) parentHWND, false);
|
|
}
|
|
|
|
ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, void* parentHWND)
|
|
{
|
|
return new HWNDComponentPeer (component, ComponentPeer::windowIgnoresMouseClicks,
|
|
(HWND) parentHWND, true);
|
|
}
|
|
|
|
|
|
juce_ImplementSingleton_SingleThreaded (HWNDComponentPeer::WindowClassHolder)
|
|
|
|
|
|
//==============================================================================
|
|
void ModifierKeys::updateCurrentModifiers() noexcept
|
|
{
|
|
currentModifiers = HWNDComponentPeer::currentModifiers;
|
|
}
|
|
|
|
ModifierKeys ModifierKeys::getCurrentModifiersRealtime() noexcept
|
|
{
|
|
HWNDComponentPeer::updateKeyModifiers();
|
|
|
|
int mouseMods = 0;
|
|
if (HWNDComponentPeer::isKeyDown (VK_LBUTTON)) mouseMods |= ModifierKeys::leftButtonModifier;
|
|
if (HWNDComponentPeer::isKeyDown (VK_RBUTTON)) mouseMods |= ModifierKeys::rightButtonModifier;
|
|
if (HWNDComponentPeer::isKeyDown (VK_MBUTTON)) mouseMods |= ModifierKeys::middleButtonModifier;
|
|
|
|
HWNDComponentPeer::currentModifiers
|
|
= HWNDComponentPeer::currentModifiers.withoutMouseButtons().withFlags (mouseMods);
|
|
|
|
return HWNDComponentPeer::currentModifiers;
|
|
}
|
|
|
|
//==============================================================================
|
|
bool KeyPress::isKeyCurrentlyDown (const int keyCode)
|
|
{
|
|
SHORT k = (SHORT) keyCode;
|
|
|
|
if ((keyCode & extendedKeyModifier) == 0)
|
|
{
|
|
if (k >= (SHORT) 'a' && k <= (SHORT) 'z')
|
|
k += (SHORT) 'A' - (SHORT) 'a';
|
|
|
|
// Only translate if extendedKeyModifier flag is not set
|
|
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 HWNDComponentPeer::isKeyDown (k);
|
|
}
|
|
|
|
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
|
|
bool offerKeyMessageToJUCEWindow (MSG& m) { return HWNDComponentPeer::offerKeyMessageToJUCEWindow (m); }
|
|
#endif
|
|
|
|
//==============================================================================
|
|
bool JUCE_CALLTYPE Process::isForegroundProcess()
|
|
{
|
|
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;)
|
|
if (HWNDComponentPeer* const wp = dynamic_cast<HWNDComponentPeer*> (ComponentPeer::getPeer (i)))
|
|
if (wp->isInside (fg))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// N/A on Windows as far as I know.
|
|
void JUCE_CALLTYPE Process::makeForegroundProcess() {}
|
|
void JUCE_CALLTYPE Process::hide() {}
|
|
|
|
//==============================================================================
|
|
static BOOL CALLBACK enumAlwaysOnTopWindows (HWND hwnd, LPARAM lParam)
|
|
{
|
|
if (IsWindowVisible (hwnd))
|
|
{
|
|
DWORD processID = 0;
|
|
GetWindowThreadProcessId (hwnd, &processID);
|
|
|
|
if (processID == GetCurrentProcessId())
|
|
{
|
|
WINDOWINFO info;
|
|
|
|
if (GetWindowInfo (hwnd, &info)
|
|
&& (info.dwExStyle & WS_EX_TOPMOST) != 0)
|
|
{
|
|
*reinterpret_cast<bool*> (lParam) = true;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bool juce_areThereAnyAlwaysOnTopWindows()
|
|
{
|
|
bool anyAlwaysOnTopFound = false;
|
|
EnumWindows (&enumAlwaysOnTopWindows, (LPARAM) &anyAlwaysOnTopFound);
|
|
return anyAlwaysOnTopFound;
|
|
}
|
|
|
|
//==============================================================================
|
|
class WindowsMessageBox : public AsyncUpdater
|
|
{
|
|
public:
|
|
WindowsMessageBox (AlertWindow::AlertIconType iconType,
|
|
const String& boxTitle, const String& m,
|
|
Component* associatedComponent, UINT extraFlags,
|
|
ModalComponentManager::Callback* cb, const bool runAsync)
|
|
: flags (extraFlags | getMessageBoxFlags (iconType)),
|
|
owner (getWindowForMessageBox (associatedComponent)),
|
|
title (boxTitle), message (m), callback (cb)
|
|
{
|
|
if (runAsync)
|
|
triggerAsyncUpdate();
|
|
}
|
|
|
|
int getResult() const
|
|
{
|
|
const int r = MessageBox (owner, message.toWideCharPointer(), title.toWideCharPointer(), flags);
|
|
return (r == IDYES || r == IDOK) ? 1 : (r == IDNO ? 2 : 0);
|
|
}
|
|
|
|
void handleAsyncUpdate() override
|
|
{
|
|
const int result = getResult();
|
|
|
|
if (callback != nullptr)
|
|
callback->modalStateFinished (result);
|
|
|
|
delete this;
|
|
}
|
|
|
|
private:
|
|
UINT flags;
|
|
HWND owner;
|
|
String title, message;
|
|
ScopedPointer<ModalComponentManager::Callback> callback;
|
|
|
|
static UINT getMessageBoxFlags (AlertWindow::AlertIconType iconType) noexcept
|
|
{
|
|
UINT flags = MB_TASKMODAL | MB_SETFOREGROUND;
|
|
|
|
switch (iconType)
|
|
{
|
|
case AlertWindow::QuestionIcon: flags |= MB_ICONQUESTION; break;
|
|
case AlertWindow::WarningIcon: flags |= MB_ICONWARNING; break;
|
|
case AlertWindow::InfoIcon: flags |= MB_ICONINFORMATION; break;
|
|
default: break;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
static HWND getWindowForMessageBox (Component* associatedComponent)
|
|
{
|
|
return associatedComponent != nullptr ? (HWND) associatedComponent->getWindowHandle() : 0;
|
|
}
|
|
};
|
|
|
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
|
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType,
|
|
const String& title, const String& message,
|
|
Component* associatedComponent)
|
|
{
|
|
WindowsMessageBox box (iconType, title, message, associatedComponent, MB_OK, 0, false);
|
|
(void) box.getResult();
|
|
}
|
|
#endif
|
|
|
|
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
|
|
const String& title, const String& message,
|
|
Component* associatedComponent,
|
|
ModalComponentManager::Callback* callback)
|
|
{
|
|
new WindowsMessageBox (iconType, title, message, associatedComponent, MB_OK, callback, true);
|
|
}
|
|
|
|
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
|
|
const String& title, const String& message,
|
|
Component* associatedComponent,
|
|
ModalComponentManager::Callback* callback)
|
|
{
|
|
ScopedPointer<WindowsMessageBox> mb (new WindowsMessageBox (iconType, title, message, associatedComponent,
|
|
MB_OKCANCEL, callback, callback != nullptr));
|
|
if (callback == nullptr)
|
|
return mb->getResult() != 0;
|
|
|
|
mb.release();
|
|
return false;
|
|
}
|
|
|
|
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
|
|
const String& title, const String& message,
|
|
Component* associatedComponent,
|
|
ModalComponentManager::Callback* callback)
|
|
{
|
|
ScopedPointer<WindowsMessageBox> mb (new WindowsMessageBox (iconType, title, message, associatedComponent,
|
|
MB_YESNOCANCEL, callback, callback != nullptr));
|
|
if (callback == nullptr)
|
|
return mb->getResult();
|
|
|
|
mb.release();
|
|
return 0;
|
|
}
|
|
|
|
//==============================================================================
|
|
bool MouseInputSource::SourceList::addSource()
|
|
{
|
|
const int numSources = sources.size();
|
|
|
|
if (numSources == 0 || canUseMultiTouch())
|
|
{
|
|
addSource (numSources, numSources == 0);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Point<float> MouseInputSource::getCurrentRawMousePosition()
|
|
{
|
|
POINT mousePos;
|
|
GetCursorPos (&mousePos);
|
|
return Point<float> ((float) mousePos.x, (float) mousePos.y);
|
|
}
|
|
|
|
void MouseInputSource::setRawMousePosition (Point<float> newPosition)
|
|
{
|
|
SetCursorPos (roundToInt (newPosition.x),
|
|
roundToInt (newPosition.y));
|
|
}
|
|
|
|
//==============================================================================
|
|
class ScreenSaverDefeater : public Timer
|
|
{
|
|
public:
|
|
ScreenSaverDefeater()
|
|
{
|
|
startTimer (10000);
|
|
timerCallback();
|
|
}
|
|
|
|
void timerCallback() override
|
|
{
|
|
if (Process::isForegroundProcess())
|
|
{
|
|
INPUT input = { 0 };
|
|
input.type = INPUT_MOUSE;
|
|
input.mi.mouseData = MOUSEEVENTF_MOVE;
|
|
|
|
SendInput (1, &input, sizeof (INPUT));
|
|
}
|
|
}
|
|
};
|
|
|
|
static ScopedPointer<ScreenSaverDefeater> screenSaverDefeater;
|
|
|
|
void Desktop::setScreenSaverEnabled (const bool isEnabled)
|
|
{
|
|
if (isEnabled)
|
|
screenSaverDefeater = nullptr;
|
|
else if (screenSaverDefeater == nullptr)
|
|
screenSaverDefeater = new ScreenSaverDefeater();
|
|
}
|
|
|
|
bool Desktop::isScreenSaverEnabled()
|
|
{
|
|
return screenSaverDefeater == nullptr;
|
|
}
|
|
|
|
//==============================================================================
|
|
void LookAndFeel::playAlertSound()
|
|
{
|
|
MessageBeep (MB_OK);
|
|
}
|
|
|
|
//==============================================================================
|
|
void SystemClipboard::copyTextToClipboard (const String& text)
|
|
{
|
|
if (OpenClipboard (0) != 0)
|
|
{
|
|
if (EmptyClipboard() != 0)
|
|
{
|
|
const size_t bytesNeeded = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()) + 4;
|
|
|
|
if (bytesNeeded > 0)
|
|
{
|
|
if (HGLOBAL bufH = GlobalAlloc (GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, bytesNeeded + sizeof (WCHAR)))
|
|
{
|
|
if (WCHAR* const data = static_cast<WCHAR*> (GlobalLock (bufH)))
|
|
{
|
|
text.copyToUTF16 (data, bytesNeeded);
|
|
GlobalUnlock (bufH);
|
|
|
|
SetClipboardData (CF_UNICODETEXT, bufH);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
String SystemClipboard::getTextFromClipboard()
|
|
{
|
|
String result;
|
|
|
|
if (OpenClipboard (0) != 0)
|
|
{
|
|
if (HANDLE bufH = GetClipboardData (CF_UNICODETEXT))
|
|
{
|
|
if (const WCHAR* const data = (const WCHAR*) GlobalLock (bufH))
|
|
{
|
|
result = String (data, (size_t) (GlobalSize (bufH) / sizeof (WCHAR)));
|
|
GlobalUnlock (bufH);
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//==============================================================================
|
|
void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/)
|
|
{
|
|
if (TopLevelWindow* tlw = dynamic_cast<TopLevelWindow*> (kioskModeComp))
|
|
tlw->setUsingNativeTitleBar (! enableOrDisable);
|
|
|
|
if (enableOrDisable)
|
|
kioskModeComp->setBounds (getDisplays().getMainDisplay().totalArea);
|
|
}
|
|
|
|
//==============================================================================
|
|
struct MonitorInfo
|
|
{
|
|
MonitorInfo (Rectangle<int> rect, bool main, double d) noexcept
|
|
: bounds (rect), dpi (d), isMain (main) {}
|
|
|
|
Rectangle<int> bounds;
|
|
double dpi;
|
|
bool isMain;
|
|
};
|
|
|
|
static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userInfo)
|
|
{
|
|
MONITORINFO info = { 0 };
|
|
info.cbSize = sizeof (info);
|
|
GetMonitorInfo (hm, &info);
|
|
const bool isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0;
|
|
double dpi = 0;
|
|
|
|
if (getDPIForMonitor != nullptr)
|
|
{
|
|
UINT dpiX = 0, dpiY = 0;
|
|
|
|
if (SUCCEEDED (getDPIForMonitor (hm, MDT_Default, &dpiX, &dpiY)))
|
|
dpi = (dpiX + dpiY) / 2.0;
|
|
}
|
|
|
|
((Array<MonitorInfo>*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain, dpi));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void Desktop::Displays::findDisplays (float masterScale)
|
|
{
|
|
setDPIAwareness();
|
|
|
|
Array<MonitorInfo> monitors;
|
|
EnumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitors);
|
|
|
|
const double globalDPI = getGlobalDPI();
|
|
|
|
if (monitors.size() == 0)
|
|
monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true, globalDPI));
|
|
|
|
// make sure the first in the list is the main monitor
|
|
for (int i = 1; i < monitors.size(); ++i)
|
|
if (monitors.getReference(i).isMain)
|
|
monitors.swap (i, 0);
|
|
|
|
RECT workArea;
|
|
SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0);
|
|
|
|
for (int i = 0; i < monitors.size(); ++i)
|
|
{
|
|
Display d;
|
|
d.userArea = d.totalArea = monitors.getReference(i).bounds / masterScale;
|
|
d.isMain = monitors.getReference(i).isMain;
|
|
d.dpi = monitors.getReference(i).dpi;
|
|
|
|
if (d.dpi == 0)
|
|
{
|
|
d.scale = masterScale;
|
|
d.dpi = globalDPI;
|
|
}
|
|
else
|
|
{
|
|
d.scale = d.dpi / 96.0;
|
|
}
|
|
|
|
if (d.isMain)
|
|
d.userArea = d.userArea.getIntersection (rectangleFromRECT (workArea) / masterScale);
|
|
|
|
displays.add (d);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
static HICON extractFileHICON (const File& file)
|
|
{
|
|
WORD iconNum = 0;
|
|
WCHAR name [MAX_PATH * 2];
|
|
file.getFullPathName().copyToUTF16 (name, sizeof (name));
|
|
|
|
return ExtractAssociatedIcon ((HINSTANCE) Process::getCurrentModuleInstanceHandle(),
|
|
name, &iconNum);
|
|
}
|
|
|
|
Image juce_createIconForFile (const File& file)
|
|
{
|
|
Image image;
|
|
if (HICON icon = extractFileHICON (file))
|
|
{
|
|
image = IconConverters::createImageFromHICON (icon);
|
|
DestroyIcon (icon);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
//==============================================================================
|
|
void* CustomMouseCursorInfo::create() const
|
|
{
|
|
const int maxW = GetSystemMetrics (SM_CXCURSOR);
|
|
const int maxH = GetSystemMetrics (SM_CYCURSOR);
|
|
|
|
Image im (image);
|
|
int hotspotX = hotspot.x;
|
|
int hotspotY = hotspot.y;
|
|
|
|
if (im.getWidth() > maxW || im.getHeight() > maxH)
|
|
{
|
|
im = im.rescaled (maxW, maxH);
|
|
|
|
hotspotX = (hotspotX * maxW) / image.getWidth();
|
|
hotspotY = (hotspotY * maxH) / image.getHeight();
|
|
}
|
|
|
|
return IconConverters::createHICONFromImage (im, FALSE, hotspotX, hotspotY);
|
|
}
|
|
|
|
void MouseCursor::deleteMouseCursor (void* const cursorHandle, const bool isStandard)
|
|
{
|
|
if (cursorHandle != nullptr && ! isStandard)
|
|
DestroyCursor ((HCURSOR) cursorHandle);
|
|
}
|
|
|
|
enum
|
|
{
|
|
hiddenMouseCursorHandle = 32500 // (arbitrary non-zero value to mark this type of cursor)
|
|
};
|
|
|
|
void* MouseCursor::createStandardMouseCursor (const MouseCursor::StandardCursorType type)
|
|
{
|
|
LPCTSTR cursorName = IDC_ARROW;
|
|
|
|
switch (type)
|
|
{
|
|
case NormalCursor:
|
|
case ParentCursor: break;
|
|
case NoCursor: return (void*) hiddenMouseCursorHandle;
|
|
case WaitCursor: cursorName = IDC_WAIT; break;
|
|
case IBeamCursor: cursorName = IDC_IBEAM; break;
|
|
case PointingHandCursor: cursorName = MAKEINTRESOURCE(32649); break;
|
|
case CrosshairCursor: cursorName = IDC_CROSS; break;
|
|
|
|
case LeftRightResizeCursor:
|
|
case LeftEdgeResizeCursor:
|
|
case RightEdgeResizeCursor: cursorName = IDC_SIZEWE; break;
|
|
|
|
case UpDownResizeCursor:
|
|
case TopEdgeResizeCursor:
|
|
case BottomEdgeResizeCursor: cursorName = IDC_SIZENS; break;
|
|
|
|
case TopLeftCornerResizeCursor:
|
|
case BottomRightCornerResizeCursor: cursorName = IDC_SIZENWSE; break;
|
|
|
|
case TopRightCornerResizeCursor:
|
|
case BottomLeftCornerResizeCursor: cursorName = IDC_SIZENESW; break;
|
|
|
|
case UpDownLeftRightResizeCursor: cursorName = IDC_SIZEALL; break;
|
|
|
|
case DraggingHandCursor:
|
|
{
|
|
static void* dragHandCursor = nullptr;
|
|
|
|
if (dragHandCursor == nullptr)
|
|
{
|
|
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 };
|
|
|
|
dragHandCursor = CustomMouseCursorInfo (ImageFileFormat::loadFrom (dragHandData, sizeof (dragHandData)), 8, 7).create();
|
|
}
|
|
|
|
return dragHandCursor;
|
|
}
|
|
|
|
case CopyingCursor:
|
|
{
|
|
static void* copyCursor = nullptr;
|
|
|
|
if (copyCursor == nullptr)
|
|
{
|
|
static unsigned char copyCursorData[] = { 71,73,70,56,57,97,21,0,21,0,145,0,0,0,0,0,255,255,255,0,
|
|
128,128,255,255,255,33,249,4,1,0,0,3,0,44,0,0,0,0,21,0, 21,0,0,2,72,4,134,169,171,16,199,98,11,79,90,71,161,93,56,111,
|
|
78,133,218,215,137,31,82,154,100,200,86,91,202,142,12,108,212,87,235,174, 15,54,214,126,237,226,37,96,59,141,16,37,18,201,142,157,230,204,51,112,
|
|
252,114,147,74,83,5,50,68,147,208,217,16,71,149,252,124,5,0,59,0,0 };
|
|
const int copyCursorSize = 119;
|
|
|
|
copyCursor = CustomMouseCursorInfo (ImageFileFormat::loadFrom (copyCursorData, copyCursorSize), 1, 3).create();
|
|
}
|
|
|
|
return copyCursor;
|
|
}
|
|
|
|
default:
|
|
jassertfalse; break;
|
|
}
|
|
|
|
HCURSOR cursorH = LoadCursor (0, cursorName);
|
|
|
|
if (cursorH == 0)
|
|
cursorH = LoadCursor (0, IDC_ARROW);
|
|
|
|
return cursorH;
|
|
}
|
|
|
|
//==============================================================================
|
|
void MouseCursor::showInWindow (ComponentPeer*) const
|
|
{
|
|
HCURSOR c = (HCURSOR) getHandle();
|
|
|
|
if (c == 0)
|
|
c = LoadCursor (0, IDC_ARROW);
|
|
else if (c == (HCURSOR) hiddenMouseCursorHandle)
|
|
c = 0;
|
|
|
|
SetCursor (c);
|
|
}
|
|
|
|
void MouseCursor::showInAllWindows() const
|
|
{
|
|
showInWindow (nullptr);
|
|
}
|
|
|