/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-7 by Raw Material Software ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License, as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with JUCE; if not, visit www.gnu.org/licenses or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ If you'd like to release a closed-source product which uses JUCE, commercial licenses are also available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ // (This file gets included by juce_win32_NativeCode.cpp, rather than being // compiled on its own). #if JUCE_INCLUDED_FILE //============================================================================== #undef GetSystemMetrics // multimon overrides this for some reason and causes a mess.. // these are in the windows SDK, but need to be repeated here for GCC.. #ifndef GET_APPCOMMAND_LPARAM #define FAPPCOMMAND_MASK 0xF000 #define GET_APPCOMMAND_LPARAM(lParam) ((short) (HIWORD (lParam) & ~FAPPCOMMAND_MASK)) #define APPCOMMAND_MEDIA_NEXTTRACK 11 #define APPCOMMAND_MEDIA_PREVIOUSTRACK 12 #define APPCOMMAND_MEDIA_STOP 13 #define APPCOMMAND_MEDIA_PLAY_PAUSE 14 #define WM_APPCOMMAND 0x0319 #endif extern void juce_repeatLastProcessPriority() throw(); // in juce_win32_Threads.cpp extern void juce_CheckCurrentlyFocusedTopLevelWindow() throw(); // in juce_TopLevelWindow.cpp extern bool juce_IsRunningInWine() throw(); #ifndef ULW_ALPHA #define ULW_ALPHA 0x00000002 #endif #ifndef AC_SRC_ALPHA #define AC_SRC_ALPHA 0x01 #endif #define DEBUG_REPAINT_TIMES 0 static HPALETTE palette = 0; static bool createPaletteIfNeeded = true; static bool shouldDeactivateTitleBar = true; static HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY) throw(); #define WM_TRAYNOTIFY WM_USER + 100 using ::abs; //============================================================================== typedef BOOL (WINAPI* UpdateLayeredWinFunc) (HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD); static UpdateLayeredWinFunc updateLayeredWindow = 0; bool Desktop::canUseSemiTransparentWindows() throw() { if (updateLayeredWindow == 0) { if (! juce_IsRunningInWine()) { HMODULE user32Mod = GetModuleHandle (_T("user32.dll")); updateLayeredWindow = (UpdateLayeredWinFunc) GetProcAddress (user32Mod, "UpdateLayeredWindow"); } } return updateLayeredWindow != 0; } //============================================================================== #undef DefWindowProc #define DefWindowProc DefWindowProcW //============================================================================== const int extendedKeyModifier = 0x10000; const int KeyPress::spaceKey = VK_SPACE; const int KeyPress::returnKey = VK_RETURN; const int KeyPress::escapeKey = VK_ESCAPE; const int KeyPress::backspaceKey = VK_BACK; const int KeyPress::deleteKey = VK_DELETE | extendedKeyModifier; const int KeyPress::insertKey = VK_INSERT | extendedKeyModifier; const int KeyPress::tabKey = VK_TAB; const int KeyPress::leftKey = VK_LEFT | extendedKeyModifier; const int KeyPress::rightKey = VK_RIGHT | extendedKeyModifier; const int KeyPress::upKey = VK_UP | extendedKeyModifier; const int KeyPress::downKey = VK_DOWN | extendedKeyModifier; const int KeyPress::homeKey = VK_HOME | extendedKeyModifier; const int KeyPress::endKey = VK_END | extendedKeyModifier; const int KeyPress::pageUpKey = VK_PRIOR | extendedKeyModifier; const int KeyPress::pageDownKey = VK_NEXT | extendedKeyModifier; const int KeyPress::F1Key = VK_F1 | extendedKeyModifier; const int KeyPress::F2Key = VK_F2 | extendedKeyModifier; const int KeyPress::F3Key = VK_F3 | extendedKeyModifier; const int KeyPress::F4Key = VK_F4 | extendedKeyModifier; const int KeyPress::F5Key = VK_F5 | extendedKeyModifier; const int KeyPress::F6Key = VK_F6 | extendedKeyModifier; const int KeyPress::F7Key = VK_F7 | extendedKeyModifier; const int KeyPress::F8Key = VK_F8 | extendedKeyModifier; const int KeyPress::F9Key = VK_F9 | extendedKeyModifier; const int KeyPress::F10Key = VK_F10 | extendedKeyModifier; const int KeyPress::F11Key = VK_F11 | extendedKeyModifier; const int KeyPress::F12Key = VK_F12 | extendedKeyModifier; const int KeyPress::F13Key = VK_F13 | extendedKeyModifier; const int KeyPress::F14Key = VK_F14 | extendedKeyModifier; const int KeyPress::F15Key = VK_F15 | extendedKeyModifier; const int KeyPress::F16Key = VK_F16 | extendedKeyModifier; const int KeyPress::numberPad0 = VK_NUMPAD0 | extendedKeyModifier; const int KeyPress::numberPad1 = VK_NUMPAD1 | extendedKeyModifier; const int KeyPress::numberPad2 = VK_NUMPAD2 | extendedKeyModifier; const int KeyPress::numberPad3 = VK_NUMPAD3 | extendedKeyModifier; const int KeyPress::numberPad4 = VK_NUMPAD4 | extendedKeyModifier; const int KeyPress::numberPad5 = VK_NUMPAD5 | extendedKeyModifier; const int KeyPress::numberPad6 = VK_NUMPAD6 | extendedKeyModifier; const int KeyPress::numberPad7 = VK_NUMPAD7 | extendedKeyModifier; const int KeyPress::numberPad8 = VK_NUMPAD8 | extendedKeyModifier; const int KeyPress::numberPad9 = VK_NUMPAD9 | extendedKeyModifier; const int KeyPress::numberPadAdd = VK_ADD | extendedKeyModifier; const int KeyPress::numberPadSubtract = VK_SUBTRACT | extendedKeyModifier; const int KeyPress::numberPadMultiply = VK_MULTIPLY | extendedKeyModifier; const int KeyPress::numberPadDivide = VK_DIVIDE | extendedKeyModifier; const int KeyPress::numberPadSeparator = VK_SEPARATOR | extendedKeyModifier; const int KeyPress::numberPadDecimalPoint = VK_DECIMAL | extendedKeyModifier; const int KeyPress::numberPadEquals = 0x92 /*VK_OEM_NEC_EQUAL*/ | extendedKeyModifier; const int KeyPress::numberPadDelete = VK_DELETE | extendedKeyModifier; const int KeyPress::playKey = 0x30000; const int KeyPress::stopKey = 0x30001; const int KeyPress::fastForwardKey = 0x30002; const int KeyPress::rewindKey = 0x30003; //============================================================================== class WindowsBitmapImage : public Image { public: //============================================================================== HBITMAP hBitmap; BITMAPV4HEADER bitmapInfo; HDC hdc; unsigned char* bitmapData; //============================================================================== WindowsBitmapImage (const PixelFormat format_, const int w, const int h, const bool clearImage) : Image (format_, w, h) { jassert (format_ == RGB || format_ == ARGB); pixelStride = (format_ == RGB) ? 3 : 4; zerostruct (bitmapInfo); bitmapInfo.bV4Size = sizeof (BITMAPV4HEADER); bitmapInfo.bV4Width = w; bitmapInfo.bV4Height = h; bitmapInfo.bV4Planes = 1; bitmapInfo.bV4CSType = 1; bitmapInfo.bV4BitCount = (unsigned short) (pixelStride * 8); if (format_ == ARGB) { bitmapInfo.bV4AlphaMask = 0xff000000; bitmapInfo.bV4RedMask = 0xff0000; bitmapInfo.bV4GreenMask = 0xff00; bitmapInfo.bV4BlueMask = 0xff; bitmapInfo.bV4V4Compression = BI_BITFIELDS; } else { bitmapInfo.bV4V4Compression = BI_RGB; } lineStride = -((w * pixelStride + 3) & ~3); HDC dc = GetDC (0); hdc = CreateCompatibleDC (dc); ReleaseDC (0, dc); SetMapMode (hdc, MM_TEXT); hBitmap = CreateDIBSection (hdc, (BITMAPINFO*) &(bitmapInfo), DIB_RGB_COLORS, (void**) &bitmapData, 0, 0); SelectObject (hdc, hBitmap); if (format_ == ARGB && clearImage) zeromem (bitmapData, abs (h * lineStride)); imageData = bitmapData - (lineStride * (h - 1)); } ~WindowsBitmapImage() { DeleteDC (hdc); DeleteObject (hBitmap); imageData = 0; // to stop the base class freeing this } void blitToWindow (HWND hwnd, HDC dc, const bool transparent, const int x, const int y, const RectangleList& maskedRegion) throw() { static HDRAWDIB hdd = 0; static bool needToCreateDrawDib = true; if (needToCreateDrawDib) { needToCreateDrawDib = false; HDC dc = GetDC (0); const int n = GetDeviceCaps (dc, BITSPIXEL); ReleaseDC (0, dc); // only open if we're not palettised if (n > 8) hdd = DrawDibOpen(); } if (createPaletteIfNeeded) { HDC dc = GetDC (0); const int n = GetDeviceCaps (dc, BITSPIXEL); ReleaseDC (0, dc); if (n <= 8) palette = CreateHalftonePalette (dc); createPaletteIfNeeded = false; } if (palette != 0) { SelectPalette (dc, palette, FALSE); RealizePalette (dc); SetStretchBltMode (dc, HALFTONE); } SetMapMode (dc, MM_TEXT); if (transparent) { POINT p, pos; SIZE size; RECT windowBounds; GetWindowRect (hwnd, &windowBounds); p.x = -x; p.y = -y; pos.x = windowBounds.left; pos.y = windowBounds.top; size.cx = windowBounds.right - windowBounds.left; size.cy = windowBounds.bottom - windowBounds.top; BLENDFUNCTION bf; bf.AlphaFormat = AC_SRC_ALPHA; bf.BlendFlags = 0; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = 0xff; if (! maskedRegion.isEmpty()) { for (RectangleList::Iterator i (maskedRegion); i.next();) { const Rectangle& r = *i.getRectangle(); ExcludeClipRect (hdc, r.getX(), r.getY(), r.getRight(), r.getBottom()); } } updateLayeredWindow (hwnd, 0, &pos, &size, hdc, &p, 0, &bf, ULW_ALPHA); } else { int savedDC = 0; if (! maskedRegion.isEmpty()) { savedDC = SaveDC (dc); for (RectangleList::Iterator i (maskedRegion); i.next();) { const Rectangle& r = *i.getRectangle(); ExcludeClipRect (dc, r.getX(), r.getY(), r.getRight(), r.getBottom()); } } const int w = getWidth(); const int h = getHeight(); if (hdd == 0) { StretchDIBits (dc, x, y, w, h, 0, 0, w, h, bitmapData, (const BITMAPINFO*) &bitmapInfo, DIB_RGB_COLORS, SRCCOPY); } else { DrawDibDraw (hdd, dc, x, y, -1, -1, (BITMAPINFOHEADER*) &bitmapInfo, bitmapData, 0, 0, w, h, 0); } if (! maskedRegion.isEmpty()) RestoreDC (dc, savedDC); } } juce_UseDebuggingNewOperator private: WindowsBitmapImage (const WindowsBitmapImage&); const WindowsBitmapImage& operator= (const WindowsBitmapImage&); }; //============================================================================== long improbableWindowNumber = 0xf965aa01; // also referenced by messaging.cpp //============================================================================== static int currentModifiers = 0; static int modifiersAtLastCallback = 0; static void updateKeyModifiers() throw() { currentModifiers &= ~(ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier | ModifierKeys::altModifier); if ((GetKeyState (VK_SHIFT) & 0x8000) != 0) currentModifiers |= ModifierKeys::shiftModifier; if ((GetKeyState (VK_CONTROL) & 0x8000) != 0) currentModifiers |= ModifierKeys::ctrlModifier; if ((GetKeyState (VK_MENU) & 0x8000) != 0) currentModifiers |= ModifierKeys::altModifier; if ((GetKeyState (VK_RMENU) & 0x8000) != 0) currentModifiers &= ~(ModifierKeys::ctrlModifier | ModifierKeys::altModifier); } void ModifierKeys::updateCurrentModifiers() throw() { currentModifierFlags = currentModifiers; } bool KeyPress::isKeyCurrentlyDown (const int keyCode) throw() { SHORT k = (SHORT) keyCode; if ((keyCode & extendedKeyModifier) == 0 && (k >= (SHORT) T('a') && k <= (SHORT) T('z'))) k += (SHORT) T('A') - (SHORT) T('a'); const SHORT translatedValues[] = { (SHORT) ',', VK_OEM_COMMA, (SHORT) '+', VK_OEM_PLUS, (SHORT) '-', VK_OEM_MINUS, (SHORT) '.', VK_OEM_PERIOD, (SHORT) ';', VK_OEM_1, (SHORT) ':', VK_OEM_1, (SHORT) '/', VK_OEM_2, (SHORT) '?', VK_OEM_2, (SHORT) '[', VK_OEM_4, (SHORT) ']', VK_OEM_6 }; for (int i = 0; i < numElementsInArray (translatedValues); i += 2) if (k == translatedValues [i]) k = translatedValues [i + 1]; return (GetKeyState (k) & 0x8000) != 0; } const ModifierKeys ModifierKeys::getCurrentModifiersRealtime() throw() { updateKeyModifiers(); currentModifiers &= ~ModifierKeys::allMouseButtonModifiers; if ((GetKeyState (VK_LBUTTON) & 0x8000) != 0) currentModifiers |= ModifierKeys::leftButtonModifier; if ((GetKeyState (VK_RBUTTON) & 0x8000) != 0) currentModifiers |= ModifierKeys::rightButtonModifier; if ((GetKeyState (VK_MBUTTON) & 0x8000) != 0) currentModifiers |= ModifierKeys::middleButtonModifier; return ModifierKeys (currentModifiers); } static int64 getMouseEventTime() throw() { static int64 eventTimeOffset = 0; static DWORD lastMessageTime = 0; const DWORD thisMessageTime = GetMessageTime(); if (thisMessageTime < lastMessageTime || lastMessageTime == 0) { lastMessageTime = thisMessageTime; eventTimeOffset = Time::currentTimeMillis() - thisMessageTime; } return eventTimeOffset + thisMessageTime; } //============================================================================== class Win32ComponentPeer : public ComponentPeer { public: //============================================================================== Win32ComponentPeer (Component* const component, const int windowStyleFlags) : ComponentPeer (component, windowStyleFlags), dontRepaint (false), fullScreen (false), isDragging (false), isMouseOver (false), hasCreatedCaret (false), currentWindowIcon (0), taskBarIcon (0), dropTarget (0) { MessageManager::getInstance() ->callFunctionOnMessageThread (&createWindowCallback, (void*) this); setTitle (component->getName()); if ((windowStyleFlags & windowHasDropShadow) != 0 && Desktop::canUseSemiTransparentWindows()) { shadower = component->getLookAndFeel().createDropShadowerForComponent (component); if (shadower != 0) shadower->setOwner (component); } else { shadower = 0; } } ~Win32ComponentPeer() { setTaskBarIcon (0); deleteAndZero (shadower); // do this before the next bit to avoid messages arriving for this window // before it's destroyed SetWindowLongPtr (hwnd, GWLP_USERDATA, 0); MessageManager::getInstance() ->callFunctionOnMessageThread (&destroyWindowCallback, (void*) hwnd); if (currentWindowIcon != 0) DestroyIcon (currentWindowIcon); if (dropTarget != 0) { dropTarget->Release(); dropTarget = 0; } } //============================================================================== void* getNativeHandle() const { return (void*) hwnd; } void setVisible (bool shouldBeVisible) { ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE); if (shouldBeVisible) InvalidateRect (hwnd, 0, 0); else lastPaintTime = 0; } void setTitle (const String& title) { SetWindowText (hwnd, title); } void setPosition (int x, int y) { offsetWithinParent (x, y); SetWindowPos (hwnd, 0, x - windowBorder.getLeft(), y - windowBorder.getTop(), 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); } void repaintNowIfTransparent() { if (isTransparent() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30) handlePaintMessage(); } void updateBorderSize() { WINDOWINFO info; info.cbSize = sizeof (info); if (GetWindowInfo (hwnd, &info)) { windowBorder = BorderSize (info.rcClient.top - info.rcWindow.top, info.rcClient.left - info.rcWindow.left, info.rcWindow.bottom - info.rcClient.bottom, info.rcWindow.right - info.rcClient.right); } } void setSize (int w, int h) { SetWindowPos (hwnd, 0, 0, 0, w + windowBorder.getLeftAndRight(), h + windowBorder.getTopAndBottom(), SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); updateBorderSize(); repaintNowIfTransparent(); } void setBounds (int x, int y, int w, int h, const bool isNowFullScreen) { fullScreen = isNowFullScreen; offsetWithinParent (x, y); SetWindowPos (hwnd, 0, x - windowBorder.getLeft(), y - windowBorder.getTop(), w + windowBorder.getLeftAndRight(), h + windowBorder.getTopAndBottom(), SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER); updateBorderSize(); repaintNowIfTransparent(); } void getBounds (int& x, int& y, int& w, int& h) const { RECT r; GetWindowRect (hwnd, &r); x = r.left; y = r.top; w = r.right - x; h = r.bottom - y; HWND parentH = GetParent (hwnd); if (parentH != 0) { GetWindowRect (parentH, &r); x -= r.left; y -= r.top; } x += windowBorder.getLeft(); y += windowBorder.getTop(); w -= windowBorder.getLeftAndRight(); h -= windowBorder.getTopAndBottom(); } int getScreenX() const { RECT r; GetWindowRect (hwnd, &r); return r.left + windowBorder.getLeft(); } int getScreenY() const { RECT r; GetWindowRect (hwnd, &r); return r.top + windowBorder.getTop(); } void relativePositionToGlobal (int& x, int& y) { RECT r; GetWindowRect (hwnd, &r); x += r.left + windowBorder.getLeft(); y += r.top + windowBorder.getTop(); } void globalPositionToRelative (int& x, int& y) { RECT r; GetWindowRect (hwnd, &r); x -= r.left + windowBorder.getLeft(); y -= r.top + windowBorder.getTop(); } void setMinimised (bool shouldBeMinimised) { if (shouldBeMinimised != isMinimised()) ShowWindow (hwnd, shouldBeMinimised ? SW_MINIMIZE : SW_SHOWNORMAL); } bool isMinimised() const { WINDOWPLACEMENT wp; wp.length = sizeof (WINDOWPLACEMENT); GetWindowPlacement (hwnd, &wp); return wp.showCmd == SW_SHOWMINIMIZED; } void setFullScreen (bool shouldBeFullScreen) { setMinimised (false); if (fullScreen != shouldBeFullScreen) { fullScreen = shouldBeFullScreen; const ComponentDeletionWatcher deletionChecker (component); if (! fullScreen) { const Rectangle boundsCopy (lastNonFullscreenBounds); if (hasTitleBar()) ShowWindow (hwnd, SW_SHOWNORMAL); if (! boundsCopy.isEmpty()) { setBounds (boundsCopy.getX(), boundsCopy.getY(), boundsCopy.getWidth(), boundsCopy.getHeight(), false); } } else { if (hasTitleBar()) ShowWindow (hwnd, SW_SHOWMAXIMIZED); else SendMessageW (hwnd, WM_SETTINGCHANGE, 0, 0); } if (! deletionChecker.hasBeenDeleted()) handleMovedOrResized(); } } bool isFullScreen() const { if (! hasTitleBar()) return fullScreen; WINDOWPLACEMENT wp; wp.length = sizeof (wp); GetWindowPlacement (hwnd, &wp); return wp.showCmd == SW_SHOWMAXIMIZED; } bool contains (int x, int y, bool trueIfInAChildWindow) const { RECT r; GetWindowRect (hwnd, &r); POINT p; p.x = x + r.left + windowBorder.getLeft(); p.y = y + r.top + windowBorder.getTop(); HWND w = WindowFromPoint (p); return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); } const BorderSize getFrameSize() const { return windowBorder; } bool setAlwaysOnTop (bool alwaysOnTop) { const bool oldDeactivate = shouldDeactivateTitleBar; shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0); SetWindowPos (hwnd, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); shouldDeactivateTitleBar = oldDeactivate; if (shadower != 0) shadower->componentBroughtToFront (*component); return true; } void toFront (bool makeActive) { setMinimised (false); const bool oldDeactivate = shouldDeactivateTitleBar; shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0); MessageManager::getInstance() ->callFunctionOnMessageThread (makeActive ? &toFrontCallback1 : &toFrontCallback2, (void*) hwnd); shouldDeactivateTitleBar = oldDeactivate; if (! makeActive) { // in this case a broughttofront call won't have occured, so do it now.. handleBroughtToFront(); } } void toBehind (ComponentPeer* other) { Win32ComponentPeer* const otherPeer = dynamic_cast (other); jassert (otherPeer != 0); // wrong type of window? if (otherPeer != 0) { setMinimised (false); SetWindowPos (hwnd, otherPeer->hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); } } bool isFocused() const { return MessageManager::getInstance() ->callFunctionOnMessageThread (&getFocusCallback, 0) == (void*) hwnd; } void grabFocus() { const bool oldDeactivate = shouldDeactivateTitleBar; shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0); MessageManager::getInstance() ->callFunctionOnMessageThread (&setFocusCallback, (void*) hwnd); shouldDeactivateTitleBar = oldDeactivate; } void textInputRequired (int /*x*/, int /*y*/) { if (! hasCreatedCaret) { hasCreatedCaret = true; CreateCaret (hwnd, (HBITMAP) 1, 0, 0); } ShowCaret (hwnd); SetCaretPos (0, 0); } void repaint (int x, int y, int w, int h) { const RECT r = { x, y, x + w, y + h }; InvalidateRect (hwnd, &r, FALSE); } void performAnyPendingRepaintsNow() { MSG m; if (component->isVisible() && PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE)) DispatchMessage (&m); } //============================================================================== static Win32ComponentPeer* getOwnerOfWindow (HWND h) throw() { if (h != 0 && GetWindowLongPtr (h, GWLP_USERDATA) == improbableWindowNumber) return (Win32ComponentPeer*) (pointer_sized_int) GetWindowLongPtr (h, 8); return 0; } //============================================================================== void setTaskBarIcon (const Image* const image) { if (image != 0) { HICON hicon = createHICONFromImage (*image, TRUE, 0, 0); if (taskBarIcon == 0) { taskBarIcon = new NOTIFYICONDATA(); taskBarIcon->cbSize = sizeof (NOTIFYICONDATA); taskBarIcon->hWnd = (HWND) hwnd; taskBarIcon->uID = (int) (pointer_sized_int) hwnd; taskBarIcon->uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; taskBarIcon->uCallbackMessage = WM_TRAYNOTIFY; taskBarIcon->hIcon = hicon; taskBarIcon->szTip[0] = 0; Shell_NotifyIcon (NIM_ADD, taskBarIcon); } else { HICON oldIcon = taskBarIcon->hIcon; taskBarIcon->hIcon = hicon; taskBarIcon->uFlags = NIF_ICON; Shell_NotifyIcon (NIM_MODIFY, taskBarIcon); DestroyIcon (oldIcon); } DestroyIcon (hicon); } else if (taskBarIcon != 0) { taskBarIcon->uFlags = 0; Shell_NotifyIcon (NIM_DELETE, taskBarIcon); DestroyIcon (taskBarIcon->hIcon); deleteAndZero (taskBarIcon); } } void setTaskBarIconToolTip (const String& toolTip) const { if (taskBarIcon != 0) { taskBarIcon->uFlags = NIF_TIP; toolTip.copyToBuffer (taskBarIcon->szTip, sizeof (taskBarIcon->szTip) - 1); Shell_NotifyIcon (NIM_MODIFY, taskBarIcon); } } //============================================================================== juce_UseDebuggingNewOperator bool dontRepaint; private: HWND hwnd; DropShadower* shadower; bool fullScreen, isDragging, isMouseOver, hasCreatedCaret; BorderSize windowBorder; HICON currentWindowIcon; NOTIFYICONDATA* taskBarIcon; IDropTarget* dropTarget; //============================================================================== class TemporaryImage : public Timer { public: //============================================================================== TemporaryImage() : image (0) { } ~TemporaryImage() { delete image; } //============================================================================== WindowsBitmapImage* getImage (const bool transparent, const int w, const int h) throw() { const Image::PixelFormat format = transparent ? Image::ARGB : Image::RGB; if (image == 0 || image->getWidth() < w || image->getHeight() < h || image->getFormat() != format) { delete image; image = new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false); } startTimer (3000); return image; } //============================================================================== void timerCallback() { stopTimer(); deleteAndZero (image); } private: WindowsBitmapImage* image; TemporaryImage (const TemporaryImage&); const TemporaryImage& operator= (const TemporaryImage&); }; TemporaryImage offscreenImageGenerator; //============================================================================== class WindowClassHolder : public DeletedAtShutdown { public: WindowClassHolder() : windowClassName ("JUCE_") { // this name has to be different for each app/dll instance because otherwise // poor old Win32 can get a bit confused (even despite it not being a process-global // window class). windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff); HINSTANCE moduleHandle = (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle(); TCHAR moduleFile [1024]; moduleFile[0] = 0; GetModuleFileName (moduleHandle, moduleFile, 1024); WORD iconNum = 0; WNDCLASSEX wcex; wcex.cbSize = sizeof (wcex); wcex.style = CS_OWNDC; wcex.lpfnWndProc = (WNDPROC) windowProc; wcex.lpszClassName = windowClassName; wcex.cbClsExtra = 0; wcex.cbWndExtra = 32; wcex.hInstance = moduleHandle; wcex.hIcon = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum); iconNum = 1; wcex.hIconSm = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum); wcex.hCursor = 0; wcex.hbrBackground = 0; wcex.lpszMenuName = 0; RegisterClassEx (&wcex); } ~WindowClassHolder() { if (ComponentPeer::getNumPeers() == 0) UnregisterClass (windowClassName, (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle()); clearSingletonInstance(); } String windowClassName; juce_DeclareSingleton_SingleThreaded_Minimal (WindowClassHolder); }; //============================================================================== static void* createWindowCallback (void* userData) { ((Win32ComponentPeer*) userData)->createWindow(); return 0; } void createWindow() { DWORD exstyle = WS_EX_ACCEPTFILES; DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN; if (hasTitleBar()) { type |= WS_OVERLAPPED; exstyle |= WS_EX_APPWINDOW; if ((styleFlags & windowHasCloseButton) != 0) { type |= WS_SYSMENU; } else { // annoyingly, windows won't let you have a min/max button without a close button jassert ((styleFlags & (windowHasMinimiseButton | windowHasMaximiseButton)) == 0); } if ((styleFlags & windowIsResizable) != 0) type |= WS_THICKFRAME; } else { type |= WS_POPUP | WS_SYSMENU; if ((styleFlags & windowAppearsOnTaskbar) == 0) exstyle |= WS_EX_TOOLWINDOW; else exstyle |= WS_EX_APPWINDOW; } if ((styleFlags & windowHasMinimiseButton) != 0) type |= WS_MINIMIZEBOX; if ((styleFlags & windowHasMaximiseButton) != 0) type |= WS_MAXIMIZEBOX; if ((styleFlags & windowIgnoresMouseClicks) != 0) exstyle |= WS_EX_TRANSPARENT; if ((styleFlags & windowIsSemiTransparent) != 0 && Desktop::canUseSemiTransparentWindows()) exstyle |= WS_EX_LAYERED; hwnd = CreateWindowEx (exstyle, WindowClassHolder::getInstance()->windowClassName, L"", type, 0, 0, 0, 0, 0, 0, 0, 0); if (hwnd != 0) { SetWindowLongPtr (hwnd, 0, 0); SetWindowLongPtr (hwnd, 8, (LONG_PTR) this); SetWindowLongPtr (hwnd, GWLP_USERDATA, improbableWindowNumber); if (dropTarget == 0) dropTarget = new JuceDropTarget (this); RegisterDragDrop (hwnd, dropTarget); updateBorderSize(); // Calling this function here is (for some reason) necessary to make Windows // correctly enable the menu items that we specify in the wm_initmenu message. GetSystemMenu (hwnd, false); } else { jassertfalse } } static void* destroyWindowCallback (void* handle) { RevokeDragDrop ((HWND) handle); DestroyWindow ((HWND) handle); return 0; } static void* toFrontCallback1 (void* h) { SetForegroundWindow ((HWND) h); return 0; } static void* toFrontCallback2 (void* h) { SetWindowPos ((HWND) h, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); return 0; } static void* setFocusCallback (void* h) { SetFocus ((HWND) h); return 0; } static void* getFocusCallback (void*) { return (void*) GetFocus(); } void offsetWithinParent (int& x, int& y) const { if (isTransparent()) { HWND parentHwnd = GetParent (hwnd); if (parentHwnd != 0) { RECT parentRect; GetWindowRect (parentHwnd, &parentRect); x += parentRect.left; y += parentRect.top; } } } bool isTransparent() const { return (GetWindowLong (hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) != 0; } inline bool hasTitleBar() const throw() { return (styleFlags & windowHasTitleBar) != 0; } void setIcon (const Image& newIcon) { HICON hicon = createHICONFromImage (newIcon, TRUE, 0, 0); if (hicon != 0) { SendMessage (hwnd, WM_SETICON, ICON_BIG, (LPARAM) hicon); SendMessage (hwnd, WM_SETICON, ICON_SMALL, (LPARAM) hicon); if (currentWindowIcon != 0) DestroyIcon (currentWindowIcon); currentWindowIcon = hicon; } } //============================================================================== void handlePaintMessage() { #if DEBUG_REPAINT_TIMES const double paintStart = Time::getMillisecondCounterHiRes(); #endif HRGN rgn = CreateRectRgn (0, 0, 0, 0); const int regionType = GetUpdateRgn (hwnd, rgn, false); PAINTSTRUCT paintStruct; HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT // message and become re-entrant, but that's OK // if something in a paint handler calls, e.g. a message box, this can become reentrant and // corrupt the image it's using to paint into, so do a check here. static bool reentrant = false; if (reentrant) { DeleteObject (rgn); EndPaint (hwnd, &paintStruct); return; } reentrant = true; // this is the rectangle to update.. int x = paintStruct.rcPaint.left; int y = paintStruct.rcPaint.top; int w = paintStruct.rcPaint.right - x; int h = paintStruct.rcPaint.bottom - y; const bool transparent = isTransparent(); if (transparent) { // it's not possible to have a transparent window with a title bar at the moment! jassert (! hasTitleBar()); RECT r; GetWindowRect (hwnd, &r); x = y = 0; w = r.right - r.left; h = r.bottom - r.top; } if (w > 0 && h > 0) { clearMaskedRegion(); WindowsBitmapImage* const offscreenImage = offscreenImageGenerator.getImage (transparent, w, h); LowLevelGraphicsSoftwareRenderer context (*offscreenImage); RectangleList* const contextClip = context.getRawClipRegion(); contextClip->clear(); context.setOrigin (-x, -y); bool needToPaintAll = true; if (regionType == COMPLEXREGION && ! transparent) { HRGN clipRgn = CreateRectRgnIndirect (&paintStruct.rcPaint); CombineRgn (rgn, rgn, clipRgn, RGN_AND); DeleteObject (clipRgn); char rgnData [8192]; const DWORD res = GetRegionData (rgn, sizeof (rgnData), (RGNDATA*) rgnData); if (res > 0 && res <= sizeof (rgnData)) { const RGNDATAHEADER* const hdr = &(((const RGNDATA*) rgnData)->rdh); if (hdr->iType == RDH_RECTANGLES && hdr->rcBound.right - hdr->rcBound.left >= w && hdr->rcBound.bottom - hdr->rcBound.top >= h) { needToPaintAll = false; const RECT* rects = (const RECT*) (rgnData + sizeof (RGNDATAHEADER)); int num = ((RGNDATA*) rgnData)->rdh.nCount; while (--num >= 0) { // (need to move this one pixel to the left because of a win32 bug) const int cx = jmax (x, rects->left - 1); const int cy = rects->top; const int cw = rects->right - cx; const int ch = rects->bottom - rects->top; if (cx + cw - x <= w && cy + ch - y <= h) { contextClip->addWithoutMerging (Rectangle (cx - x, cy - y, cw, ch)); } else { needToPaintAll = true; break; } ++rects; } } } } if (needToPaintAll) { contextClip->clear(); contextClip->addWithoutMerging (Rectangle (0, 0, w, h)); } if (transparent) { RectangleList::Iterator i (*contextClip); while (i.next()) { const Rectangle& r = *i.getRectangle(); offscreenImage->clear (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } } // if the component's not opaque, this won't draw properly unless the platform can support this jassert (Desktop::canUseSemiTransparentWindows() || component->isOpaque()); updateCurrentModifiers(); handlePaint (context); if (! dontRepaint) offscreenImage->blitToWindow (hwnd, dc, transparent, x, y, maskedRegion); } DeleteObject (rgn); EndPaint (hwnd, &paintStruct); reentrant = false; #ifndef JUCE_GCC //xxx should add this fn for gcc.. _fpreset(); // because some graphics cards can unmask FP exceptions #endif lastPaintTime = Time::getMillisecondCounter(); #if DEBUG_REPAINT_TIMES const double elapsed = Time::getMillisecondCounterHiRes() - paintStart; Logger::outputDebugString (T("repaint time: ") + String (elapsed, 2)); #endif } //============================================================================== void doMouseMove (const int x, const int y) { static uint32 lastMouseTime = 0; // this can be set to throttle the mouse-messages to less than a // certain number per second, as things can get unresponsive // if each drag or move callback has to do a lot of work. const int maxMouseMovesPerSecond = 60; const int64 mouseEventTime = getMouseEventTime(); if (! isMouseOver) { isMouseOver = true; TRACKMOUSEEVENT tme; tme.cbSize = sizeof (tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; tme.dwHoverTime = 0; if (! TrackMouseEvent (&tme)) { jassertfalse; } updateKeyModifiers(); handleMouseEnter (x, y, mouseEventTime); } else if (! isDragging) { if (((unsigned int) x) < (unsigned int) component->getWidth() && ((unsigned int) y) < (unsigned int) component->getHeight()) { RECT r; GetWindowRect (hwnd, &r); POINT p; p.x = x + r.left + windowBorder.getLeft(); p.y = y + r.top + windowBorder.getTop(); if (WindowFromPoint (p) == hwnd) { const uint32 now = Time::getMillisecondCounter(); if (now > lastMouseTime + 1000 / maxMouseMovesPerSecond) { lastMouseTime = now; handleMouseMove (x, y, mouseEventTime); } } } } else { const uint32 now = Time::getMillisecondCounter(); if (now > lastMouseTime + 1000 / maxMouseMovesPerSecond) { lastMouseTime = now; handleMouseDrag (x, y, mouseEventTime); } } } void doMouseDown (const int x, const int y, const WPARAM wParam) { if (GetCapture() != hwnd) SetCapture (hwnd); doMouseMove (x, y); currentModifiers &= ~ModifierKeys::allMouseButtonModifiers; if ((wParam & MK_LBUTTON) != 0) currentModifiers |= ModifierKeys::leftButtonModifier; if ((wParam & MK_RBUTTON) != 0) currentModifiers |= ModifierKeys::rightButtonModifier; if ((wParam & MK_MBUTTON) != 0) currentModifiers |= ModifierKeys::middleButtonModifier; updateKeyModifiers(); isDragging = true; handleMouseDown (x, y, getMouseEventTime()); } void doMouseUp (const int x, const int y, const WPARAM wParam) { int numButtons = 0; if ((wParam & MK_LBUTTON) != 0) ++numButtons; if ((wParam & MK_RBUTTON) != 0) ++numButtons; if ((wParam & MK_MBUTTON) != 0) ++numButtons; const int oldModifiers = currentModifiers; // update the currentmodifiers only after the callback, so the callback // knows which button was released. currentModifiers &= ~ModifierKeys::allMouseButtonModifiers; if ((wParam & MK_LBUTTON) != 0) currentModifiers |= ModifierKeys::leftButtonModifier; if ((wParam & MK_RBUTTON) != 0) currentModifiers |= ModifierKeys::rightButtonModifier; if ((wParam & MK_MBUTTON) != 0) currentModifiers |= ModifierKeys::middleButtonModifier; updateKeyModifiers(); isDragging = false; // release the mouse capture if the user's not still got a button down if (numButtons == 0 && hwnd == GetCapture()) ReleaseCapture(); handleMouseUp (oldModifiers, x, y, getMouseEventTime()); } void doCaptureChanged() { if (isDragging) { RECT wr; GetWindowRect (hwnd, &wr); const DWORD mp = GetMessagePos(); doMouseUp (GET_X_LPARAM (mp) - wr.left - windowBorder.getLeft(), GET_Y_LPARAM (mp) - wr.top - windowBorder.getTop(), (WPARAM) getMouseEventTime()); } } void doMouseExit() { if (isMouseOver) { isMouseOver = false; RECT wr; GetWindowRect (hwnd, &wr); const DWORD mp = GetMessagePos(); handleMouseExit (GET_X_LPARAM (mp) - wr.left - windowBorder.getLeft(), GET_Y_LPARAM (mp) - wr.top - windowBorder.getTop(), getMouseEventTime()); } } void doMouseWheel (const WPARAM wParam, const bool isVertical) { updateKeyModifiers(); const int amount = jlimit (-1000, 1000, (int) (0.75f * (short) HIWORD (wParam))); handleMouseWheel (isVertical ? 0 : amount, isVertical ? amount : 0, getMouseEventTime()); } //============================================================================== void sendModifierKeyChangeIfNeeded() { if (modifiersAtLastCallback != currentModifiers) { modifiersAtLastCallback = currentModifiers; handleModifierKeysChange(); } } bool doKeyUp (const WPARAM key) { updateKeyModifiers(); switch (key) { case VK_SHIFT: case VK_CONTROL: case VK_MENU: case VK_CAPITAL: case VK_LWIN: case VK_RWIN: case VK_APPS: case VK_NUMLOCK: case VK_SCROLL: case VK_LSHIFT: case VK_RSHIFT: case VK_LCONTROL: case VK_LMENU: case VK_RCONTROL: case VK_RMENU: sendModifierKeyChangeIfNeeded(); } return handleKeyUpOrDown(); } 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(); used = handleKeyPress (extendedKeyModifier | (int) key, 0) || used; break; case VK_ADD: case VK_SUBTRACT: case VK_MULTIPLY: case VK_DIVIDE: case VK_SEPARATOR: case VK_DECIMAL: used = handleKeyUpOrDown(); break; default: used = handleKeyUpOrDown(); { MSG msg; if (! PeekMessage (&msg, hwnd, WM_CHAR, WM_DEADCHAR, PM_NOREMOVE)) { // if there isn't a WM_CHAR or WM_DEADCHAR message pending, we need to // manually generate the key-press event that matches this key-down. const UINT keyChar = MapVirtualKey (key, 2); used = handleKeyPress ((int) LOWORD (keyChar), 0) || used; } } break; } return used; } bool doKeyChar (int key, const LPARAM flags) { updateKeyModifiers(); juce_wchar textChar = (juce_wchar) key; const int virtualScanCode = (flags >> 16) & 0xff; if (key >= '0' && key <= '9') { switch (virtualScanCode) // check for a numeric keypad scan-code { case 0x52: case 0x4f: case 0x50: case 0x51: case 0x4b: case 0x4c: case 0x4d: case 0x47: case 0x48: case 0x49: key = (key - '0') + KeyPress::numberPad0; break; default: break; } } else { // convert the scan code to an unmodified character code.. const UINT virtualKey = MapVirtualKey (virtualScanCode, 1); UINT keyChar = MapVirtualKey (virtualKey, 2); keyChar = LOWORD (keyChar); if (keyChar != 0) key = (int) keyChar; // avoid sending junk text characters for some control-key combinations if (textChar < ' ' && (currentModifiers & (ModifierKeys::ctrlModifier | ModifierKeys::altModifier)) != 0) textChar = 0; } return handleKeyPress (key, textChar); } bool doAppCommand (const LPARAM lParam) { int key = 0; switch (GET_APPCOMMAND_LPARAM (lParam)) { case APPCOMMAND_MEDIA_PLAY_PAUSE: key = KeyPress::playKey; break; case APPCOMMAND_MEDIA_STOP: key = KeyPress::stopKey; break; case APPCOMMAND_MEDIA_NEXTTRACK: key = KeyPress::fastForwardKey; break; case APPCOMMAND_MEDIA_PREVIOUSTRACK: key = KeyPress::rewindKey; break; } if (key != 0) { updateKeyModifiers(); if (hwnd == GetActiveWindow()) { handleKeyPress (key, 0); return true; } } return false; } //============================================================================== class JuceDropTarget : public IDropTarget { public: JuceDropTarget (Win32ComponentPeer* const owner_) : owner (owner_), refCount (1) { } virtual ~JuceDropTarget() { jassert (refCount == 0); } HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result) { if (id == IID_IUnknown || id == IID_IDropTarget) { AddRef(); *result = this; return S_OK; } *result = 0; return E_NOINTERFACE; } ULONG __stdcall AddRef() { return ++refCount; } ULONG __stdcall Release() { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; } HRESULT __stdcall DragEnter (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) { updateFileList (pDataObject); int x = mousePos.x, y = mousePos.y; owner->globalPositionToRelative (x, y); owner->handleFileDragMove (files, x, y); *pdwEffect = DROPEFFECT_COPY; return S_OK; } HRESULT __stdcall DragLeave() { owner->handleFileDragExit (files); return S_OK; } HRESULT __stdcall DragOver (DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) { int x = mousePos.x, y = mousePos.y; owner->globalPositionToRelative (x, y); owner->handleFileDragMove (files, x, y); *pdwEffect = DROPEFFECT_COPY; return S_OK; } HRESULT __stdcall Drop (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) { updateFileList (pDataObject); int x = mousePos.x, y = mousePos.y; owner->globalPositionToRelative (x, y); owner->handleFileDragDrop (files, x, y); *pdwEffect = DROPEFFECT_COPY; return S_OK; } private: Win32ComponentPeer* const owner; int refCount; StringArray files; void updateFileList (IDataObject* const pDataObject) { files.clear(); FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 }; if (pDataObject->GetData (&format, &medium) == S_OK) { const SIZE_T totalLen = GlobalSize (medium.hGlobal); const LPDROPFILES pDropFiles = (const LPDROPFILES) GlobalLock (medium.hGlobal); unsigned int i = 0; if (pDropFiles->fWide) { const WCHAR* const fname = (WCHAR*) (((const char*) pDropFiles) + sizeof (DROPFILES)); for (;;) { unsigned int len = 0; while (i + len < totalLen && fname [i + len] != 0) ++len; if (len == 0) break; files.add (String (fname + i, len)); i += len + 1; } } else { const char* const fname = ((const char*) pDropFiles) + sizeof (DROPFILES); for (;;) { unsigned int len = 0; while (i + len < totalLen && fname [i + len] != 0) ++len; if (len == 0) break; files.add (String (fname + i, len)); i += len + 1; } } GlobalUnlock (medium.hGlobal); } } JuceDropTarget (const JuceDropTarget&); const JuceDropTarget& operator= (const JuceDropTarget&); }; void doSettingChange() { Desktop::getInstance().refreshMonitorSizes(); if (fullScreen && ! isMinimised()) { const Rectangle r (component->getParentMonitorArea()); SetWindowPos (hwnd, 0, r.getX(), r.getY(), r.getWidth(), r.getHeight(), SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING); } } //============================================================================== public: static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) { Win32ComponentPeer* const peer = getOwnerOfWindow (h); if (peer != 0) return peer->peerWindowProc (h, message, wParam, lParam); return DefWindowProc (h, message, wParam, lParam); } private: LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) { { const MessageManagerLock messLock; if (isValidPeer (this)) { switch (message) { case WM_NCHITTEST: if (hasTitleBar()) break; return HTCLIENT; //============================================================================== case WM_PAINT: handlePaintMessage(); return 0; case WM_NCPAINT: if (wParam != 1) handlePaintMessage(); if (hasTitleBar()) break; return 0; case WM_ERASEBKGND: case WM_NCCALCSIZE: if (hasTitleBar()) break; return 1; //============================================================================== case WM_MOUSEMOVE: doMouseMove (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam)); return 0; case WM_MOUSELEAVE: doMouseExit(); return 0; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: doMouseDown (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam), wParam); return 0; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: doMouseUp (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam), wParam); return 0; case WM_CAPTURECHANGED: doCaptureChanged(); return 0; case WM_NCMOUSEMOVE: if (hasTitleBar()) break; return 0; case 0x020A: /* WM_MOUSEWHEEL */ doMouseWheel (wParam, true); return 0; case 0x020E: /* WM_MOUSEHWHEEL */ doMouseWheel (wParam, false); return 0; //============================================================================== case WM_WINDOWPOSCHANGING: if ((styleFlags & (windowHasTitleBar | windowIsResizable)) == (windowHasTitleBar | windowIsResizable)) { WINDOWPOS* const wp = (WINDOWPOS*) lParam; if ((wp->flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE)) { if (constrainer != 0) { const Rectangle current (component->getX() - windowBorder.getLeft(), component->getY() - windowBorder.getTop(), component->getWidth() + windowBorder.getLeftAndRight(), component->getHeight() + windowBorder.getTopAndBottom()); constrainer->checkBounds (wp->x, wp->y, wp->cx, wp->cy, current, Desktop::getInstance().getAllMonitorDisplayAreas().getBounds(), wp->y != current.getY() && wp->y + wp->cy == current.getBottom(), wp->x != current.getX() && wp->x + wp->cx == current.getRight(), wp->y == current.getY() && wp->y + wp->cy != current.getBottom(), wp->x == current.getX() && wp->x + wp->cx != current.getRight()); } } } return 0; case WM_WINDOWPOSCHANGED: handleMovedOrResized(); if (dontRepaint) break; // needed for non-accelerated openGL windows to draw themselves correctly.. else return 0; //============================================================================== case WM_KEYDOWN: case WM_SYSKEYDOWN: if (doKeyDown (wParam)) return 0; break; case WM_KEYUP: case WM_SYSKEYUP: if (doKeyUp (wParam)) return 0; break; case WM_CHAR: if (doKeyChar ((int) wParam, lParam)) return 0; break; case WM_APPCOMMAND: if (doAppCommand (lParam)) return TRUE; break; //============================================================================== case WM_SETFOCUS: updateKeyModifiers(); handleFocusGain(); break; case WM_KILLFOCUS: if (hasCreatedCaret) { hasCreatedCaret = false; DestroyCaret(); } handleFocusLoss(); break; case WM_ACTIVATEAPP: // Windows does weird things to process priority when you swap apps, // so this forces an update when the app is brought to the front if (wParam != FALSE) juce_repeatLastProcessPriority(); else Desktop::getInstance().setKioskModeComponent (0); // turn kiosk mode off if we lose focus juce_CheckCurrentlyFocusedTopLevelWindow(); modifiersAtLastCallback = -1; return 0; case WM_ACTIVATE: if (LOWORD (wParam) == WA_ACTIVE || LOWORD (wParam) == WA_CLICKACTIVE) { modifiersAtLastCallback = -1; updateKeyModifiers(); if (isMinimised()) { component->repaint(); handleMovedOrResized(); if (! isValidMessageListener()) return 0; } if (LOWORD (wParam) == WA_CLICKACTIVE && component->isCurrentlyBlockedByAnotherModalComponent()) { int mx, my; component->getMouseXYRelative (mx, my); Component* const underMouse = component->getComponentAt (mx, my); if (underMouse != 0 && underMouse->isCurrentlyBlockedByAnotherModalComponent()) Component::getCurrentlyModalComponent()->inputAttemptWhenModal(); return 0; } handleBroughtToFront(); if (component->isCurrentlyBlockedByAnotherModalComponent()) Component::getCurrentlyModalComponent()->toFront (true); return 0; } break; case WM_NCACTIVATE: // while a temporary window is being shown, prevent Windows from deactivating the // title bars of our main windows. if (wParam == 0 && ! shouldDeactivateTitleBar) wParam = TRUE; // change this and let it get passed to the DefWindowProc. break; case WM_MOUSEACTIVATE: if (! component->getMouseClickGrabsKeyboardFocus()) return MA_NOACTIVATE; break; case WM_SHOWWINDOW: if (wParam != 0) handleBroughtToFront(); break; case WM_CLOSE: if (! component->isCurrentlyBlockedByAnotherModalComponent()) handleUserClosingWindow(); return 0; case WM_QUIT: JUCEApplication::quit(); return 0; //============================================================================== case WM_TRAYNOTIFY: if (component->isCurrentlyBlockedByAnotherModalComponent()) { if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN || lParam == WM_LBUTTONDBLCLK || lParam == WM_LBUTTONDBLCLK) { Component* const current = Component::getCurrentlyModalComponent(); if (current != 0) current->inputAttemptWhenModal(); } } else { const int oldModifiers = currentModifiers; MouseEvent e (0, 0, ModifierKeys::getCurrentModifiersRealtime(), component, getMouseEventTime(), 0, 0, getMouseEventTime(), 1, false); if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK) e.mods = ModifierKeys (e.mods.getRawFlags() | ModifierKeys::leftButtonModifier); else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK) e.mods = ModifierKeys (e.mods.getRawFlags() | ModifierKeys::rightButtonModifier); if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN) { SetFocus (hwnd); SetForegroundWindow (hwnd); component->mouseDown (e); } else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP) { e.mods = ModifierKeys (oldModifiers); component->mouseUp (e); } else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_LBUTTONDBLCLK) { e.mods = ModifierKeys (oldModifiers); component->mouseDoubleClick (e); } else if (lParam == WM_MOUSEMOVE) { component->mouseMove (e); } } break; //============================================================================== case WM_SYNCPAINT: return 0; case WM_PALETTECHANGED: InvalidateRect (h, 0, 0); break; case WM_DISPLAYCHANGE: InvalidateRect (h, 0, 0); createPaletteIfNeeded = true; // intentional fall-through... case WM_SETTINGCHANGE: // note the fall-through in the previous case! doSettingChange(); break; case WM_INITMENU: if (! hasTitleBar()) { if (isFullScreen()) { EnableMenuItem ((HMENU) wParam, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem ((HMENU) wParam, SC_MOVE, MF_BYCOMMAND | MF_GRAYED); } else if (! isMinimised()) { EnableMenuItem ((HMENU) wParam, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED); } } break; case WM_SYSCOMMAND: switch (wParam & 0xfff0) { case SC_CLOSE: if (sendInputAttemptWhenModalMessage()) return 0; if (hasTitleBar()) { PostMessage (h, WM_CLOSE, 0, 0); return 0; } break; case SC_KEYMENU: // (NB mustn't call sendInputAttemptWhenModalMessage() here because of very // obscure situations that can arise if a modal loop is started from an alt-key // keypress). if (hasTitleBar() && h == GetCapture()) ReleaseCapture(); break; case SC_MAXIMIZE: if (sendInputAttemptWhenModalMessage()) return 0; setFullScreen (true); return 0; case SC_MINIMIZE: if (sendInputAttemptWhenModalMessage()) return 0; if (! hasTitleBar()) { setMinimised (true); return 0; } break; case SC_RESTORE: if (sendInputAttemptWhenModalMessage()) return 0; if (hasTitleBar()) { if (isFullScreen()) { setFullScreen (false); return 0; } } else { if (isMinimised()) setMinimised (false); else if (isFullScreen()) setFullScreen (false); return 0; } break; } break; case WM_NCLBUTTONDOWN: case WM_NCRBUTTONDOWN: case WM_NCMBUTTONDOWN: sendInputAttemptWhenModalMessage(); break; //case WM_IME_STARTCOMPOSITION; // return 0; case WM_GETDLGCODE: return DLGC_WANTALLKEYS; default: break; } } } // (the message manager lock exits before calling this, to avoid deadlocks if // this calls into non-juce windows) return DefWindowProc (h, message, wParam, lParam); } bool sendInputAttemptWhenModalMessage() { if (component->isCurrentlyBlockedByAnotherModalComponent()) { Component* const current = Component::getCurrentlyModalComponent(); if (current != 0) current->inputAttemptWhenModal(); return true; } return false; } Win32ComponentPeer (const Win32ComponentPeer&); const Win32ComponentPeer& operator= (const Win32ComponentPeer&); }; ComponentPeer* Component::createNewPeer (int styleFlags, void* /*nativeWindowToAttachTo*/) { return new Win32ComponentPeer (this, styleFlags); } juce_ImplementSingleton_SingleThreaded (Win32ComponentPeer::WindowClassHolder); //============================================================================== void SystemTrayIconComponent::setIconImage (const Image& newImage) { Win32ComponentPeer* const wp = dynamic_cast (getPeer()); if (wp != 0) wp->setTaskBarIcon (&newImage); } void SystemTrayIconComponent::setIconTooltip (const String& tooltip) { Win32ComponentPeer* const wp = dynamic_cast (getPeer()); if (wp != 0) wp->setTaskBarIconToolTip (tooltip); } //============================================================================== void juce_setWindowStyleBit (HWND h, const int styleType, const int feature, const bool bitIsSet) throw() { DWORD val = GetWindowLong (h, styleType); if (bitIsSet) val |= feature; else val &= ~feature; SetWindowLongPtr (h, styleType, val); SetWindowPos (h, 0, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOSENDCHANGING); } //============================================================================== bool Process::isForegroundProcess() throw() { HWND fg = GetForegroundWindow(); if (fg == 0) return true; DWORD processId = 0; GetWindowThreadProcessId (fg, &processId); return processId == GetCurrentProcessId(); } //============================================================================== bool AlertWindow::showNativeDialogBox (const String& title, const String& bodyText, bool isOkCancel) { return MessageBox (0, bodyText, title, MB_SETFOREGROUND | (isOkCancel ? MB_OKCANCEL : MB_OK)) == IDOK; } //============================================================================== void Desktop::getMousePosition (int& x, int& y) throw() { POINT mousePos; GetCursorPos (&mousePos); x = mousePos.x; y = mousePos.y; } void Desktop::setMousePosition (int x, int y) throw() { SetCursorPos (x, y); } //============================================================================== class ScreenSaverDefeater : public Timer, public DeletedAtShutdown { public: ScreenSaverDefeater() throw() { startTimer (10000); timerCallback(); } ~ScreenSaverDefeater() {} void timerCallback() { if (Process::isForegroundProcess()) { // simulate a shift key getting pressed.. INPUT input[2]; input[0].type = INPUT_KEYBOARD; input[0].ki.wVk = VK_SHIFT; input[0].ki.dwFlags = 0; input[0].ki.dwExtraInfo = 0; input[1].type = INPUT_KEYBOARD; input[1].ki.wVk = VK_SHIFT; input[1].ki.dwFlags = KEYEVENTF_KEYUP; input[1].ki.dwExtraInfo = 0; SendInput (2, input, sizeof (INPUT)); } } }; static ScreenSaverDefeater* screenSaverDefeater = 0; void Desktop::setScreenSaverEnabled (const bool isEnabled) throw() { if (isEnabled) { deleteAndZero (screenSaverDefeater); } else if (screenSaverDefeater == 0) { screenSaverDefeater = new ScreenSaverDefeater(); } } bool Desktop::isScreenSaverEnabled() throw() { return screenSaverDefeater == 0; } /* (The code below is the "correct" way to disable the screen saver, but it completely fails on winXP when the saver is password-protected...) static bool juce_screenSaverEnabled = true; void Desktop::setScreenSaverEnabled (const bool isEnabled) throw() { juce_screenSaverEnabled = isEnabled; SetThreadExecutionState (isEnabled ? ES_CONTINUOUS : (ES_DISPLAY_REQUIRED | ES_CONTINUOUS)); } bool Desktop::isScreenSaverEnabled() throw() { return juce_screenSaverEnabled; } */ //============================================================================== void juce_setKioskComponent (Component* kioskModeComponent, bool enableOrDisable) { if (enableOrDisable) kioskModeComponent->setBounds (Desktop::getInstance().getMainMonitorArea (false)); } //============================================================================== static BOOL CALLBACK enumMonitorsProc (HMONITOR, HDC, LPRECT r, LPARAM userInfo) { Array * const monitorCoords = (Array *) userInfo; monitorCoords->add (Rectangle (r->left, r->top, r->right - r->left, r->bottom - r->top)); return TRUE; } void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool clipToWorkArea) throw() { EnumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitorCoords); // make sure the first in the list is the main monitor for (int i = 1; i < monitorCoords.size(); ++i) if (monitorCoords[i].getX() == 0 && monitorCoords[i].getY() == 0) monitorCoords.swap (i, 0); if (monitorCoords.size() == 0) { RECT r; GetWindowRect (GetDesktopWindow(), &r); monitorCoords.add (Rectangle (r.left, r.top, r.right - r.left, r.bottom - r.top)); } if (clipToWorkArea) { // clip the main monitor to the active non-taskbar area RECT r; SystemParametersInfo (SPI_GETWORKAREA, 0, &r, 0); Rectangle& screen = monitorCoords.getReference (0); screen.setPosition (jmax (screen.getX(), r.left), jmax (screen.getY(), r.top)); screen.setSize (jmin (screen.getRight(), r.right) - screen.getX(), jmin (screen.getBottom(), r.bottom) - screen.getY()); } } //============================================================================== static Image* createImageFromHBITMAP (HBITMAP bitmap) throw() { Image* im = 0; if (bitmap != 0) { BITMAP bm; if (GetObject (bitmap, sizeof (BITMAP), &bm) && bm.bmWidth > 0 && bm.bmHeight > 0) { HDC tempDC = GetDC (0); HDC dc = CreateCompatibleDC (tempDC); ReleaseDC (0, tempDC); SelectObject (dc, bitmap); im = new Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true); for (int y = bm.bmHeight; --y >= 0;) { for (int x = bm.bmWidth; --x >= 0;) { COLORREF col = GetPixel (dc, x, y); im->setPixelAt (x, y, Colour ((uint8) GetRValue (col), (uint8) GetGValue (col), (uint8) GetBValue (col))); } } DeleteDC (dc); } } return im; } static Image* createImageFromHICON (HICON icon) throw() { ICONINFO info; if (GetIconInfo (icon, &info)) { Image* const mask = createImageFromHBITMAP (info.hbmMask); if (mask == 0) return 0; Image* const image = createImageFromHBITMAP (info.hbmColor); if (image == 0) return mask; for (int y = image->getHeight(); --y >= 0;) { for (int x = image->getWidth(); --x >= 0;) { const float brightness = mask->getPixelAt (x, y).getBrightness(); if (brightness > 0.0f) image->multiplyAlphaAt (x, y, 1.0f - brightness); } } delete mask; return image; } return 0; } static HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY) throw() { HBITMAP mask = CreateBitmap (image.getWidth(), image.getHeight(), 1, 1, 0); ICONINFO info; info.fIcon = isIcon; info.xHotspot = hotspotX; info.yHotspot = hotspotY; info.hbmMask = mask; HICON hi = 0; if (SystemStats::getOperatingSystemType() >= SystemStats::WinXP) { WindowsBitmapImage bitmap (Image::ARGB, image.getWidth(), image.getHeight(), true); Graphics g (bitmap); g.drawImageAt (&image, 0, 0); info.hbmColor = bitmap.hBitmap; hi = CreateIconIndirect (&info); } else { HBITMAP colour = CreateCompatibleBitmap (GetDC (0), image.getWidth(), image.getHeight()); HDC colDC = CreateCompatibleDC (GetDC (0)); HDC alphaDC = CreateCompatibleDC (GetDC (0)); SelectObject (colDC, colour); SelectObject (alphaDC, mask); for (int y = image.getHeight(); --y >= 0;) { for (int x = image.getWidth(); --x >= 0;) { const Colour c (image.getPixelAt (x, y)); SetPixel (colDC, x, y, COLORREF (c.getRed() | (c.getGreen() << 8) | (c.getBlue() << 16))); SetPixel (alphaDC, x, y, COLORREF (0xffffff - (c.getAlpha() | (c.getAlpha() << 8) | (c.getAlpha() << 16)))); } } DeleteDC (colDC); DeleteDC (alphaDC); info.hbmColor = colour; hi = CreateIconIndirect (&info); DeleteObject (colour); } DeleteObject (mask); return hi; } Image* juce_createIconForFile (const File& file) { Image* image = 0; TCHAR filename [1024]; file.getFullPathName().copyToBuffer (filename, 1023); WORD iconNum = 0; HICON icon = ExtractAssociatedIcon ((HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle(), filename, &iconNum); if (icon != 0) { image = createImageFromHICON (icon); DestroyIcon (icon); } return image; } //============================================================================== void* juce_createMouseCursorFromImage (const Image& image, int hotspotX, int hotspotY) throw() { const int maxW = GetSystemMetrics (SM_CXCURSOR); const int maxH = GetSystemMetrics (SM_CYCURSOR); const Image* im = ℑ Image* newIm = 0; if (image.getWidth() > maxW || image.getHeight() > maxH) { im = newIm = image.createCopy (maxW, maxH); hotspotX = (hotspotX * maxW) / image.getWidth(); hotspotY = (hotspotY * maxH) / image.getHeight(); } void* cursorH = 0; const SystemStats::OperatingSystemType os = SystemStats::getOperatingSystemType(); if (os == SystemStats::WinXP) { cursorH = (void*) createHICONFromImage (*im, FALSE, hotspotX, hotspotY); } else { const int stride = (maxW + 7) >> 3; uint8* const andPlane = (uint8*) juce_calloc (stride * maxH); uint8* const xorPlane = (uint8*) juce_calloc (stride * maxH); int index = 0; for (int y = 0; y < maxH; ++y) { for (int x = 0; x < maxW; ++x) { const unsigned char bit = (unsigned char) (1 << (7 - (x & 7))); const Colour pixelColour (im->getPixelAt (x, y)); if (pixelColour.getAlpha() < 127) andPlane [index + (x >> 3)] |= bit; else if (pixelColour.getBrightness() >= 0.5f) xorPlane [index + (x >> 3)] |= bit; } index += stride; } cursorH = CreateCursor (0, hotspotX, hotspotY, maxW, maxH, andPlane, xorPlane); juce_free (andPlane); juce_free (xorPlane); } delete newIm; return cursorH; } void juce_deleteMouseCursor (void* const cursorHandle, const bool isStandard) throw() { if (cursorHandle != 0 && ! isStandard) DestroyCursor ((HCURSOR) cursorHandle); } void* juce_createStandardMouseCursor (MouseCursor::StandardCursorType type) throw() { LPCTSTR cursorName = IDC_ARROW; switch (type) { case MouseCursor::NormalCursor: cursorName = IDC_ARROW; break; case MouseCursor::NoCursor: return 0; case MouseCursor::DraggingHandCursor: { static void* dragHandCursor = 0; if (dragHandCursor == 0) { static const unsigned char dragHandData[] = { 71,73,70,56,57,97,16,0,16,0,145,2,0,0,0,0,255,255,255,0,0,0,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0, 16,0,0,2,52,148,47,0,200,185,16,130,90,12,74,139,107,84,123,39, 132,117,151,116,132,146,248,60,209,138, 98,22,203,114,34,236,37,52,77,217,247,154,191,119,110,240,193,128,193,95,163,56,60,234,98,135,2,0,59 }; Image* const image = ImageFileFormat::loadFrom ((const char*) dragHandData, sizeof (dragHandData)); dragHandCursor = juce_createMouseCursorFromImage (*image, 8, 7); delete image; } return dragHandCursor; } case MouseCursor::WaitCursor: cursorName = IDC_WAIT; break; case MouseCursor::IBeamCursor: cursorName = IDC_IBEAM; break; case MouseCursor::PointingHandCursor: cursorName = MAKEINTRESOURCE(32649); break; case MouseCursor::LeftRightResizeCursor: case MouseCursor::LeftEdgeResizeCursor: case MouseCursor::RightEdgeResizeCursor: cursorName = IDC_SIZEWE; break; case MouseCursor::UpDownResizeCursor: case MouseCursor::TopEdgeResizeCursor: case MouseCursor::BottomEdgeResizeCursor: cursorName = IDC_SIZENS; break; case MouseCursor::TopLeftCornerResizeCursor: case MouseCursor::BottomRightCornerResizeCursor: cursorName = IDC_SIZENWSE; break; case MouseCursor::TopRightCornerResizeCursor: case MouseCursor::BottomLeftCornerResizeCursor: cursorName = IDC_SIZENESW; break; case MouseCursor::UpDownLeftRightResizeCursor: cursorName = IDC_SIZEALL; break; case MouseCursor::CrosshairCursor: cursorName = IDC_CROSS; break; case MouseCursor::CopyingCursor: // can't seem to find one of these in the win32 list.. break; } HCURSOR cursorH = LoadCursor (0, cursorName); if (cursorH == 0) cursorH = LoadCursor (0, IDC_ARROW); return (void*) cursorH; } //============================================================================== void MouseCursor::showInWindow (ComponentPeer*) const throw() { SetCursor ((HCURSOR) getHandle()); } void MouseCursor::showInAllWindows() const throw() { showInWindow (0); } //============================================================================== //============================================================================== class JuceDropSource : public IDropSource { int refCount; public: JuceDropSource() : refCount (1) { } virtual ~JuceDropSource() { jassert (refCount == 0); } HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result) { if (id == IID_IUnknown || id == IID_IDropSource) { AddRef(); *result = this; return S_OK; } *result = 0; return E_NOINTERFACE; } ULONG __stdcall AddRef() { return ++refCount; } ULONG __stdcall Release() { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; } HRESULT __stdcall QueryContinueDrag (BOOL escapePressed, DWORD keys) { if (escapePressed) return DRAGDROP_S_CANCEL; if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0) return DRAGDROP_S_DROP; return S_OK; } HRESULT __stdcall GiveFeedback (DWORD) { return DRAGDROP_S_USEDEFAULTCURSORS; } }; class JuceEnumFormatEtc : public IEnumFORMATETC { public: JuceEnumFormatEtc (const FORMATETC* const format_) : refCount (1), format (format_), index (0) { } virtual ~JuceEnumFormatEtc() { jassert (refCount == 0); } HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result) { if (id == IID_IUnknown || id == IID_IEnumFORMATETC) { AddRef(); *result = this; return S_OK; } *result = 0; return E_NOINTERFACE; } ULONG __stdcall AddRef() { return ++refCount; } ULONG __stdcall Release() { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; } HRESULT __stdcall Clone (IEnumFORMATETC** result) { if (result == 0) return E_POINTER; JuceEnumFormatEtc* const newOne = new JuceEnumFormatEtc (format); newOne->index = index; *result = newOne; return S_OK; } HRESULT __stdcall Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched) { if (pceltFetched != 0) *pceltFetched = 0; else if (celt != 1) return S_FALSE; if (index == 0 && celt > 0 && lpFormatEtc != 0) { copyFormatEtc (lpFormatEtc [0], *format); ++index; if (pceltFetched != 0) *pceltFetched = 1; return S_OK; } return S_FALSE; } HRESULT __stdcall Skip (ULONG celt) { if (index + (int) celt >= 1) return S_FALSE; index += celt; return S_OK; } HRESULT __stdcall Reset() { index = 0; return S_OK; } private: int refCount; const FORMATETC* const format; int index; static void copyFormatEtc (FORMATETC& dest, const FORMATETC& source) { dest = source; if (source.ptd != 0) { dest.ptd = (DVTARGETDEVICE*) CoTaskMemAlloc (sizeof (DVTARGETDEVICE)); *(dest.ptd) = *(source.ptd); } } JuceEnumFormatEtc (const JuceEnumFormatEtc&); const JuceEnumFormatEtc& operator= (const JuceEnumFormatEtc&); }; class JuceDataObject : public IDataObject { JuceDropSource* const dropSource; const FORMATETC* const format; const STGMEDIUM* const medium; int refCount; JuceDataObject (const JuceDataObject&); const JuceDataObject& operator= (const JuceDataObject&); public: JuceDataObject (JuceDropSource* const dropSource_, const FORMATETC* const format_, const STGMEDIUM* const medium_) : dropSource (dropSource_), format (format_), medium (medium_), refCount (1) { } virtual ~JuceDataObject() { jassert (refCount == 0); } HRESULT __stdcall QueryInterface (REFIID id, void __RPC_FAR* __RPC_FAR* result) { if (id == IID_IUnknown || id == IID_IDataObject) { AddRef(); *result = this; return S_OK; } *result = 0; return E_NOINTERFACE; } ULONG __stdcall AddRef() { return ++refCount; } ULONG __stdcall Release() { jassert (refCount > 0); const int r = --refCount; if (r == 0) delete this; return r; } HRESULT __stdcall GetData (FORMATETC __RPC_FAR* pFormatEtc, STGMEDIUM __RPC_FAR* pMedium) { if (pFormatEtc->tymed == format->tymed && pFormatEtc->cfFormat == format->cfFormat && pFormatEtc->dwAspect == format->dwAspect) { pMedium->tymed = format->tymed; pMedium->pUnkForRelease = 0; if (format->tymed == TYMED_HGLOBAL) { const SIZE_T len = GlobalSize (medium->hGlobal); void* const src = GlobalLock (medium->hGlobal); void* const dst = GlobalAlloc (GMEM_FIXED, len); memcpy (dst, src, len); GlobalUnlock (medium->hGlobal); pMedium->hGlobal = dst; return S_OK; } } return DV_E_FORMATETC; } HRESULT __stdcall QueryGetData (FORMATETC __RPC_FAR* f) { if (f == 0) return E_INVALIDARG; if (f->tymed == format->tymed && f->cfFormat == format->cfFormat && f->dwAspect == format->dwAspect) return S_OK; return DV_E_FORMATETC; } HRESULT __stdcall GetCanonicalFormatEtc (FORMATETC __RPC_FAR*, FORMATETC __RPC_FAR* pFormatEtcOut) { pFormatEtcOut->ptd = 0; return E_NOTIMPL; } HRESULT __stdcall EnumFormatEtc (DWORD direction, IEnumFORMATETC __RPC_FAR *__RPC_FAR *result) { if (result == 0) return E_POINTER; if (direction == DATADIR_GET) { *result = new JuceEnumFormatEtc (format); return S_OK; } *result = 0; return E_NOTIMPL; } HRESULT __stdcall GetDataHere (FORMATETC __RPC_FAR*, STGMEDIUM __RPC_FAR*) { return DATA_E_FORMATETC; } HRESULT __stdcall SetData (FORMATETC __RPC_FAR*, STGMEDIUM __RPC_FAR*, BOOL) { return E_NOTIMPL; } HRESULT __stdcall DAdvise (FORMATETC __RPC_FAR*, DWORD, IAdviseSink __RPC_FAR*, DWORD __RPC_FAR*) { return OLE_E_ADVISENOTSUPPORTED; } HRESULT __stdcall DUnadvise (DWORD) { return E_NOTIMPL; } HRESULT __stdcall EnumDAdvise (IEnumSTATDATA __RPC_FAR *__RPC_FAR *) { return OLE_E_ADVISENOTSUPPORTED; } }; static HDROP createHDrop (const StringArray& fileNames) throw() { int totalChars = 0; for (int i = fileNames.size(); --i >= 0;) totalChars += fileNames[i].length() + 1; HDROP hDrop = (HDROP) GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof (DROPFILES) + sizeof (WCHAR) * (totalChars + 2)); if (hDrop != 0) { LPDROPFILES pDropFiles = (LPDROPFILES) GlobalLock (hDrop); pDropFiles->pFiles = sizeof (DROPFILES); pDropFiles->fWide = true; WCHAR* fname = (WCHAR*) (((char*) pDropFiles) + sizeof (DROPFILES)); for (int i = 0; i < fileNames.size(); ++i) { fileNames[i].copyToBuffer (fname, 2048); fname += fileNames[i].length() + 1; } *fname = 0; GlobalUnlock (hDrop); } return hDrop; } static bool performDragDrop (FORMATETC* const format, STGMEDIUM* const medium, const DWORD whatToDo) throw() { JuceDropSource* const source = new JuceDropSource(); JuceDataObject* const data = new JuceDataObject (source, format, medium); DWORD effect; const HRESULT res = DoDragDrop (data, source, whatToDo, &effect); data->Release(); source->Release(); return res == DRAGDROP_S_DROP; } bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove) { FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 }; medium.hGlobal = createHDrop (files); return performDragDrop (&format, &medium, canMove ? (DROPEFFECT_COPY | DROPEFFECT_MOVE) : DROPEFFECT_COPY); } bool DragAndDropContainer::performExternalDragDropOfText (const String& text) { FORMATETC format = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 }; const int numChars = text.length(); medium.hGlobal = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, (numChars + 2) * sizeof (WCHAR)); char* d = (char*) GlobalLock (medium.hGlobal); text.copyToBuffer ((WCHAR*) d, numChars + 1); format.cfFormat = CF_UNICODETEXT; GlobalUnlock (medium.hGlobal); return performDragDrop (&format, &medium, DROPEFFECT_COPY | DROPEFFECT_MOVE); } #endif