/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * */ #include "common/system.h" #include "common/queue.h" #include "common/textconsole.h" #include "bagel/mfc/afxwin.h" #include "bagel/mfc/wingdi.h" #include "bagel/mfc/win_hand.h" #include "bagel/mfc/gfx/dialog_template.h" namespace Bagel { namespace MFC { IMPLEMENT_DYNAMIC(CWnd, CCmdTarget) BEGIN_MESSAGE_MAP(CWnd, CCmdTarget) ON_WM_ACTIVATE() ON_WM_CLOSE() ON_WM_DESTROY() ON_WM_NCDESTROY() ON_WM_DRAWITEM() ON_WM_SETFONT() ON_WM_SETCURSOR() ON_WM_SHOWWINDOW() ON_WM_QUERYNEWPALETTE() END_MESSAGE_MAP() CWnd *CWnd::FromHandlePermanent(HWND hWnd) { auto *pMap = AfxGetApp()->afxMapWnd(); assert(pMap); return pMap->LookupPermanent(hWnd); } CWnd *CWnd::FromHandle(HWND hWnd) { return FromHandlePermanent(hWnd); } CWnd::CWnd() : m_hWnd(this) { CWinApp *app = AfxGetApp(); auto *pMap = app->afxMapWnd(true); assert(pMap != nullptr); pMap->SetPermanent(m_hWnd, this); // Defaults _hFont = app->getDefaultFont(); _hPen = app->getDefaultPen(); _hBrush = app->getDefaultBrush(); _hPalette = app->getSystemDefaultPalette(); } CWnd::~CWnd() { AfxGetApp()->afxUpdateWnds().remove(this); // Although we check the flag here, currently it's // hardcoded to be on for ScummVM if ((m_nClassStyle & CS_OWNDC) && _pDC) { _pDC->DeleteDC(); delete _pDC; _pDC = nullptr; } DestroyWindow(); // Remove wnd from the map auto *pMap = AfxGetApp()->afxMapWnd(); assert(pMap != nullptr); pMap->RemoveHandle(m_hWnd); } bool CWnd::Create(const char *lpszClassName, const char *lpszWindowName, uint32 dwStyle, const RECT &rect, CWnd *pParentWnd, unsigned int nID, CCreateContext *pContext) { m_pParentWnd = pParentWnd; m_nStyle = dwStyle; _controlId = nID; // Set up create structure CREATESTRUCT cs; cs.x = rect.left; cs.y = rect.top; cs.cx = rect.right - rect.left; cs.cy = rect.bottom - rect.top; cs.style = dwStyle; cs.lpCreateParams = pContext; // Trigger pre-create event if (!PreCreateWindow(cs)) return false; // Create the actual window content _windowRect.left = cs.x; _windowRect.top = cs.y; _windowRect.right = cs.x + cs.cx; _windowRect.bottom = cs.y + cs.cy; _windowText = lpszWindowName; // Get the screen area RECT screenRect; screenRect.left = screenRect.top = 0; screenRect.right = cs.cx; screenRect.bottom = cs.cy; ClientToScreen(&screenRect); // Get the class details WNDCLASS wc; GetClassInfo(nullptr, lpszClassName, &wc); m_nClassStyle = wc.style; if (m_pParentWnd) m_pParentWnd->_children[nID] = this; SendMessage(WM_CREATE, 0, (LPARAM)&cs); Invalidate(); return true; } bool CWnd::CreateEx(uint32 dwExStyle, const char *lpszClassName, const char *lpszWindowName, uint32 dwStyle, const RECT &rect, CWnd *pParentWnd, unsigned int nID, void *lpParam /* = nullptr */) { return CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(), nID, lpParam); } bool CWnd::CreateEx(uint32 dwExStyle, const char *lpszClassName, const char *lpszWindowName, uint32 dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, LPARAM nIDorHMenu, void *lpParam) { // Set up create structure CREATESTRUCT cs; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.lpCreateParams = lpParam; // Trigger pre-create event if (!PreCreateWindow(cs)) return false; // Create the actual window content _windowRect.left = cs.x; _windowRect.top = cs.y; _windowRect.right = cs.x + cs.cx; _windowRect.bottom = cs.y + cs.cy; _windowText = lpszWindowName; // Get the screen area RECT screenRect; screenRect.left = screenRect.top = 0; screenRect.right = cs.cx; screenRect.bottom = cs.cy; ClientToScreen(&screenRect); // Get the class details WNDCLASS wc; GetClassInfo(nullptr, lpszClassName, &wc); m_nClassStyle = wc.style; SendMessage(WM_CREATE, 0, (LPARAM)&cs); Invalidate(); return true; } const MSG *CWnd::GetCurrentMessage() { return &AfxGetApp()->_currentMessage; } CWnd *CWnd::GetParent() const { return m_pParentWnd; } CWnd *CWnd::GetTopLevelFrame() { CWnd *p = this; while (p->GetParent() != nullptr) p = p->GetParent(); return p; } Common::Array CWnd::GetSafeParents(bool includeSelf) const { Common::Array results; bool hasParentDialog = false; for (const CWnd *wnd = includeSelf ? this : m_pParentWnd; wnd; wnd = wnd->m_pParentWnd) { if (dynamic_cast(wnd)) { if (!hasParentDialog) { hasParentDialog = true; results.push_back(wnd); } } else { results.push_back(wnd); } } return results; } Common::Array CWnd::GetSafeParents(bool includeSelf) { Common::Array results; bool hasParentDialog = !includeSelf && dynamic_cast(this) != nullptr; for (CWnd *wnd = includeSelf ? this : m_pParentWnd; wnd; wnd = wnd->m_pParentWnd) { if (dynamic_cast(wnd)) { if (!hasParentDialog) { hasParentDialog = true; results.push_back(wnd); } } else { results.push_back(wnd); } } return results; } HWND CWnd::GetSafeHwnd() const { return m_hWnd; } void CWnd::ShowWindow(int nCmdShow) { assert(nCmdShow == SW_SHOW || nCmdShow == SW_SHOWNORMAL || nCmdShow == SW_HIDE); if (nCmdShow == SW_SHOW || nCmdShow == SW_SHOWNORMAL) { m_nStyle |= WS_VISIBLE; if (dynamic_cast(this) || dynamic_cast(this)) SetActiveWindow(); } else { m_nStyle &= ~WS_VISIBLE; } Invalidate(false); SendMessage(WM_SHOWWINDOW, (m_nStyle & WS_VISIBLE) != 0); } bool CWnd::EnableWindow(bool bEnable) { bool oldEnabled = (_itemState & ODS_DISABLED) == 0; if (bEnable) _itemState &= ~ODS_DISABLED; else _itemState |= ODS_DISABLED; // Flag the control to be redrawn Invalidate(); return oldEnabled; } void CWnd::UpdateWindow() { // If there's a pending paint, do it now MSG msg; if (PeekMessage(&msg, m_hWnd, WM_PAINT, WM_PAINT, PM_REMOVE) || IsWindowDirty()) { msg.hwnd = m_hWnd; msg.message = WM_PAINT; TranslateMessage(&msg); DispatchMessage(&msg); } } bool CWnd::RedrawWindow(LPCRECT lpRectUpdate, CRgn *prgnUpdate, unsigned int flags) { if (flags & RDW_INVALIDATE) { // Invalidate the region or rectangle if (prgnUpdate != nullptr) { error("TODO: InvalidateRgn"); } else { InvalidateRect(lpRectUpdate, !(flags & RDW_NOERASE)); } } if (flags & RDW_VALIDATE) { // Optionally validate instead if (prgnUpdate != nullptr) { error("TODO: ValidateRgn"); } else { ValidateRect(lpRectUpdate); } } // Step 2: Invalidate overlapping children if (flags & RDW_ALLCHILDREN) { RECT rcUpdate = lpRectUpdate ? *lpRectUpdate : GetClientRect(); for (auto &node : _children) { CWnd *pChild = node._value; if (pChild->IsWindowVisible()) { RECT rcChild = pChild->GetWindowRectInParentCoords(); if (RectsIntersect(rcUpdate, rcChild)) { pChild->Invalidate(); } } } } if (flags & (RDW_UPDATENOW | RDW_ERASENOW)) { // Send WM_PAINT immediately UpdateWindow(); } return true; } void CWnd::SetActiveWindow() { AfxGetApp()->SetActiveWindow(this); } void CWnd::SetFocus() { AfxGetApp()->SetFocus(this); } CWnd *CWnd::GetFocus() const { return AfxGetApp()->GetFocus(); } void CWnd::OnActivate(unsigned int nState, CWnd *pWndOther, bool bMinimized) { if (nState != WA_INACTIVE) { // We're becoming active - ensure we repaint Invalidate(); } } void CWnd::OnClose() { DestroyWindow(); } void CWnd::DestroyWindow() { // Lose focus if it currently has it auto *app = AfxGetApp(); if (app->GetFocus() == this) app->SetFocus(nullptr); bool isActiveWindow = IsActiveWindow(); if (isActiveWindow) SendMessage(WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, false), 0); // Mark as not needed any repainting Validate(); // Destroy and detach child controls for (auto &node : _children) { CWnd *child = node._value; child->DestroyWindow(); } _children.clear(); // Free any owned controls for (CWnd *ctl : _ownedControls) delete ctl; _ownedControls.clear(); // If still attached to parent, remove from it if (m_pParentWnd) { m_pParentWnd->_ownedControls.remove(this); m_pParentWnd->_children.erase(_controlId); } SendMessage(WM_DESTROY); // Null the m_pParentWnd field, just in case m_pParentWnd = nullptr; // Send NCDESTROY. Warning: for at least view classes, // the default PostNcDestroy deletes the view. // So no class fields can be accessed beyond this point SendMessage(WM_NCDESTROY); // If it's the active window, pop it if (isActiveWindow) AfxGetApp()->PopActiveWindow(); } int CWnd::GetWindowText(CString &rString) const { rString = _windowText; return rString.size(); } int CWnd::GetWindowText(char *lpszStringBuf, int nMaxCount) const { Common::strcpy_s(lpszStringBuf, nMaxCount, _windowText.c_str()); return strlen(lpszStringBuf); } bool CWnd::SetWindowText(const char *lpszString) { _windowText = lpszString; Invalidate(); return true; } unsigned int CWnd::GetState() const { return _itemState; } void CWnd::SetStyle(uint32 nStyle) { m_nStyle = nStyle; Invalidate(); } CDC *CWnd::GetDC() { if (_pDC != nullptr) { // Return persistent DC return _pDC; } else if (_windowRect.isEmpty()) { // Window hasn't yet been created, so return // a full screen DC HDC hdc = MFC::GetDC(nullptr); CDC *dc = new CDC(); dc->Attach(hdc); return dc; } else { // Return a new DC for the window CDC::Impl *hDC = new CDC::Impl(this); hDC->Attach(_hFont); hDC->Attach(_hPen); hDC->Attach(_hBrush); hDC->selectPalette(_hPalette, true); RECT screenRect; screenRect.left = screenRect.top = 0; screenRect.right = _windowRect.width(); screenRect.bottom = _windowRect.height(); ClientToScreen(&screenRect); hDC->setScreenRect(screenRect); CDC *pDC = new CDC(); pDC->Attach(hDC); if (m_nClassStyle & CS_OWNDC) _pDC = pDC; return pDC; } } int CWnd::ReleaseDC(CDC *pDC) { if (pDC && pDC == _pDC) { // Wnd has persistent dc assert(m_nClassStyle & CS_OWNDC); } else { delete pDC; } return 1; } bool CWnd::PostMessage(unsigned int message, WPARAM wParam, LPARAM lParam) { return AfxGetApp()->PostMessage(m_hWnd, message, wParam, lParam); } LRESULT CWnd::SendMessage(unsigned int message, WPARAM wParam, LPARAM lParam) { // Don't send messages to already destroyed windows if (!m_hWnd) return 0; auto &msg = AfxGetApp()->_currentMessage; msg.hwnd = m_hWnd; msg.message = message; msg.wParam = wParam; msg.lParam = lParam; LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); // FIXME: In a properly emulated control painting, // painting a window background should exclude drawing on // any area covered by child controls. Technically each // control has it's own window, unlike here in ScummVM, // where all the DCs are sharing the single physical screen. // As a hack to fix issues with controls getting drawn over, // any paint to a wnd will redraw any child controls if (message == WM_PAINT) { for (auto &child : _children) child._value->RedrawWindow(nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); } // Handle messages that get sent to child controls if (isRecursiveMessage(message)) { for (auto &ctl : _children) ctl._value->SendMessage(message, wParam, lParam); } return lResult; } bool CWnd::isRecursiveMessage(unsigned int message) { // TODO: Need to refactor these to use the // SendMessageToDescendants instead of this return message == WM_SHOWWINDOW || message == WM_ENABLE; } bool CWnd::OnWndMsg(unsigned int message, WPARAM wParam, LPARAM lParam, LRESULT *pResult) { LRESULT lResult = 0; const AFX_MSGMAP_ENTRY *lpEntry; // Special case for commands if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return false; } else if (message == WM_ACTIVATE) { OnActivate(LOWORD(wParam), nullptr, false); lResult = 1; goto LReturnTrue; } // Ignoring messages to hidden controls if (!IsWindowVisible()) { static const uint16 MESSAGES[] = { WM_PAINT, WM_ERASEBKGND, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_SETFOCUS, WM_KILLFOCUS, WM_KEYDOWN, WM_KEYUP, WM_CHAR, WM_COMMAND, WM_TIMER, 0 }; for (const uint16 *msgP = MESSAGES; *msgP; ++msgP) { if (message == *msgP) return true; } } // Special cases we don't currently support #define UNHANDLED(MSG) if (message == MSG) \ error(#MSG " not currently supported") UNHANDLED(WM_NOTIFY); UNHANDLED(WM_SETTINGCHANGE); #undef UNHANDLED // Look up the message in the message map lpEntry = LookupMessage(message); if (!lpEntry) return false; assert(message < 0xC000); union MessageMapFunctions mmf; mmf.pfn = lpEntry->pfn; int nSig; nSig = lpEntry->nSig; switch (nSig) { case AfxSig_bD: lResult = (this->*mmf.pfn_bD)(CDC::FromHandle((HDC)wParam)); break; case AfxSig_bb: // AfxSig_bb, AfxSig_bw, AfxSig_bh lResult = (this->*mmf.pfn_bb)((bool)wParam); break; case AfxSig_bWww: // really AfxSig_bWiw lResult = (this->*mmf.pfn_bWww)(CWnd::FromHandle((HWND)wParam), (short)LOWORD(lParam), HIWORD(lParam)); break; case AfxSig_bWCDS: lResult = (this->*mmf.pfn_bWCDS)(CWnd::FromHandle((HWND)wParam), (COPYDATASTRUCT *)lParam); break; case AfxSig_bHELPINFO: lResult = (this->*mmf.pfn_bHELPINFO)((HELPINFO *)lParam); break; case AfxSig_hDWw: // Special case for OnCtlColor to avoid too many temporary objects error("Unsupported OnCtlColor"); break; case AfxSig_hDw: // Special case for CtlColor to avoid too many temporary objects error("Unsupported WM_REFLECT_BASE"); break; case AfxSig_iwWw: lResult = (this->*mmf.pfn_iwWw)(LOWORD(wParam), CWnd::FromHandle((HWND)lParam), HIWORD(wParam)); break; case AfxSig_iww: lResult = (this->*mmf.pfn_iww)(LOWORD(wParam), HIWORD(wParam)); break; case AfxSig_iWww: // Really AfxSig_iWiw lResult = (this->*mmf.pfn_iWww)(CWnd::FromHandle((HWND)wParam), (short)LOWORD(lParam), HIWORD(lParam)); break; case AfxSig_is: lResult = (this->*mmf.pfn_is)((uint16 *)lParam); break; case AfxSig_lwl: lResult = (this->*mmf.pfn_lwl)(wParam, lParam); break; case AfxSig_lwwM: lResult = (this->*mmf.pfn_lwwM)((unsigned int)LOWORD(wParam), (unsigned int)HIWORD(wParam), (CMenu *)CMenu::FromHandle((HMENU)lParam)); break; case AfxSig_vv: (this->*mmf.pfn_vv)(); break; case AfxSig_vw: // AfxSig_vb, AfxSig_vh (this->*mmf.pfn_vw)(wParam); break; case AfxSig_vww: (this->*mmf.pfn_vww)((unsigned int)wParam, (unsigned int)lParam); break; case AfxSig_vvii: (this->*mmf.pfn_vvii)((short)LOWORD(lParam), (short)HIWORD(lParam)); break; case AfxSig_vwww: (this->*mmf.pfn_vwww)(wParam, LOWORD(lParam), HIWORD(lParam)); break; case AfxSig_vwii: (this->*mmf.pfn_vwii)(wParam, LOWORD(lParam), HIWORD(lParam)); break; case AfxSig_vwl: (this->*mmf.pfn_vwl)(wParam, lParam); break; case AfxSig_vbWW: (this->*mmf.pfn_vbWW)(m_hWnd == (HWND)lParam, CWnd::FromHandle((HWND)lParam), CWnd::FromHandle((HWND)wParam)); break; case AfxSig_vD: (this->*mmf.pfn_vD)(CDC::FromHandle((HDC)wParam)); break; case AfxSig_vM: (this->*mmf.pfn_vM)(CMenu::FromHandle((HMENU)wParam)); break; case AfxSig_vMwb: (this->*mmf.pfn_vMwb)(CMenu::FromHandle((HMENU)wParam), LOWORD(lParam), (bool)HIWORD(lParam)); break; case AfxSig_vW: (this->*mmf.pfn_vW)(CWnd::FromHandle((HWND)wParam)); break; case AfxSig_vW2: (this->*mmf.pfn_vW)(CWnd::FromHandle((HWND)lParam)); break; case AfxSig_vWww: (this->*mmf.pfn_vWww)(CWnd::FromHandle((HWND)wParam), LOWORD(lParam), HIWORD(lParam)); break; case AfxSig_vWp: { CPoint point((uint32)lParam); (this->*mmf.pfn_vWp)(CWnd::FromHandle((HWND)wParam), point); } break; case AfxSig_vWh: (this->*mmf.pfn_vWh)(CWnd::FromHandle((HWND)wParam), (HANDLE)lParam); break; case AfxSig_vwW: (this->*mmf.pfn_vwW)(wParam, CWnd::FromHandle((HWND)lParam)); break; case AfxSig_vwWb: (this->*mmf.pfn_vwWb)((unsigned int)(LOWORD(wParam)), CWnd::FromHandle((HWND)lParam), (bool)HIWORD(wParam)); break; case AfxSig_vwwW: case AfxSig_vwwx: { // special case for WM_VSCROLL and WM_HSCROLL ASSERT(message == WM_VSCROLL || message == WM_HSCROLL || message == WM_VSCROLL + WM_REFLECT_BASE || message == WM_HSCROLL + WM_REFLECT_BASE); int nScrollCode = (short)LOWORD(wParam); int nPos = (short)HIWORD(wParam); if (lpEntry->nSig == AfxSig_vwwW) (this->*mmf.pfn_vwwW)(nScrollCode, nPos, CWnd::FromHandle((HWND)lParam)); else (this->*mmf.pfn_vwwx)(nScrollCode, nPos); } break; case AfxSig_vs: (this->*mmf.pfn_vs)((uint16 *)lParam); break; case AfxSig_vws: (this->*mmf.pfn_vws)((unsigned int)wParam, (const char *)lParam); break; case AfxSig_vOWNER: (this->*mmf.pfn_vOWNER)((int)wParam, (uint16 *)lParam); lResult = true; break; case AfxSig_iis: lResult = (this->*mmf.pfn_iis)((int)wParam, (uint16 *)lParam); break; case AfxSig_wp: { CPoint point((uint32)lParam); lResult = (this->*mmf.pfn_wp)(point); } break; case AfxSig_bv: lResult = (this->*mmf.pfn_bv)() ? 1 : 0; break; case AfxSig_wv: lResult = (this->*mmf.pfn_wv)(); break; case AfxSig_vCALC: (this->*mmf.pfn_vCALC)((bool)wParam, (NCCALCSIZE_PARAMS *)lParam); break; case AfxSig_vPOS: (this->*mmf.pfn_vPOS)((WINDOWPOS *)lParam); break; case AfxSig_vwwh: (this->*mmf.pfn_vwwh)(LOWORD(wParam), HIWORD(wParam), (HANDLE)lParam); break; case AfxSig_vwp: { CPoint point((uint32)lParam); (this->*mmf.pfn_vwp)(wParam, point); break; } case AfxSig_vwSIZING: (this->*mmf.pfn_vwl)(wParam, lParam); lResult = true; break; case AfxSig_bwsp: lResult = (this->*mmf.pfn_bwsp)(LOWORD(wParam), (short)HIWORD(wParam), CPoint(LOWORD(lParam), HIWORD(lParam))); if (!lResult) return false; break; case AfxSig_vwpb: (this->*mmf.pfn_vFb)((HFONT)wParam, (bool)lParam); break; default: error("Unknown AFX signature"); break; } LReturnTrue: if (pResult != nullptr) *pResult = lResult; return true; } bool CWnd::Validate() { return ValidateRect(nullptr); } bool CWnd::ValidateRect(LPCRECT lpRect) { if (!lpRect) { // Remove entire area _updateRect = Common::Rect(); } else { if (_updateRect.isEmpty() || _updateRect.contains(*lpRect)) { _updateRect = Common::Rect(); } } return true; } void CWnd::Invalidate(bool bErase) { CRect clientRect; GetClientRect(&clientRect); InvalidateRect(&clientRect, bErase); } bool CWnd::InvalidateRect(LPCRECT lpRect, bool bErase) { if (lpRect) _updateRect.extend(*lpRect); else _updateRect = Common::Rect(0, 0, _windowRect.width(), _windowRect.height()); assert(_updateRect.left >= 0 && _updateRect.top >= 0 && _updateRect.right <= _windowRect.width() && _updateRect.bottom <= _windowRect.bottom); // Add wnd to update list AfxGetApp()->afxUpdateWnds().add(this); return true; } void CWnd::GetWindowRect(LPRECT lpRect) const { lpRect->left = _windowRect.left; lpRect->top = _windowRect.top; lpRect->right = _windowRect.right; lpRect->bottom = _windowRect.bottom; } bool CWnd::GetUpdateRect(LPRECT lpRect, bool bErase) { if (lpRect) *lpRect = RectToRECT(_updateRect); _updateErase = bErase; return IsWindowDirty(); } bool CWnd::GetClientRect(LPRECT lpRect) const { lpRect->left = 0; lpRect->top = 0; lpRect->right = _windowRect.width(); lpRect->bottom = _windowRect.height(); return true; } RECT CWnd::GetClientRect() const { RECT r; GetClientRect(&r); return r; } bool CWnd::PointInClientRect(const POINT &pt) const { RECT clientRect; GetClientRect(&clientRect); return clientRect.contains(pt); } void CWnd::ClientToScreen(LPPOINT lpPoint) const { for (const CWnd *wnd : GetSafeParents()) { lpPoint->x += wnd->_windowRect.left; lpPoint->y += wnd->_windowRect.top; } } void CWnd::ClientToScreen(LPRECT lpRect) const { for (const CWnd *wnd : GetSafeParents()) { lpRect->left += wnd->_windowRect.left; lpRect->top += wnd->_windowRect.top; lpRect->right += wnd->_windowRect.left; lpRect->bottom += wnd->_windowRect.top; } } void CWnd::ScreenToClient(LPPOINT lpPoint) const { for (const CWnd *wnd : GetSafeParents()) { lpPoint->x -= wnd->_windowRect.left; lpPoint->y -= wnd->_windowRect.top; } } void CWnd::ScreenToClient(LPRECT lpRect) const { for (const CWnd *wnd : GetSafeParents()) { lpRect->left -= wnd->_windowRect.left; lpRect->top -= wnd->_windowRect.top; lpRect->right -= wnd->_windowRect.left; lpRect->bottom -= wnd->_windowRect.top; } } RECT CWnd::GetWindowRectInParentCoords() const { RECT rc; GetWindowRect(&rc); // Convert to parent's client coordinates CWnd *pParent = GetParent(); if (pParent != nullptr) { pParent->ScreenToClient(&rc); } return rc; } void CWnd::MoveWindow(LPCRECT lpRect, bool bRepaint) { _windowRect = *lpRect; ValidateRect(nullptr); if (_pDC) { // Get the screen area RECT screenRect; screenRect.left = screenRect.top = 0; screenRect.right = _windowRect.width(); screenRect.bottom = _windowRect.height(); ClientToScreen(&screenRect); _pDC->impl()->setScreenRect(screenRect); } // Iterate through all child controls. We won't // change their relative position, but doing so will // cause their screen surface area to be updated for (auto &ctl : _children) { const Common::Rect &r = ctl._value->_windowRect; RECT ctlRect; ctlRect.left = r.left; ctlRect.top = r.top; ctlRect.right = r.right; ctlRect.bottom = r.bottom; ctl._value->MoveWindow(&ctlRect, false); } if (bRepaint) InvalidateRect(nullptr, true); } void CWnd::MoveWindow(int x, int y, int nWidth, int nHeight, bool bRepaint) { RECT r; r.left = x; r.top = y; r.right = x + nWidth; r.bottom = y + nHeight; MoveWindow(&r, bRepaint); } HDC CWnd::BeginPaint(LPPAINTSTRUCT lpPaint) { CDC *dc = GetDC(); lpPaint->hdc = dc->m_hDC; lpPaint->fErase = _updateErase; lpPaint->rcPaint = RectToRECT(_updateRect); lpPaint->fRestore = false; lpPaint->fIncUpdate = false; Common::fill(lpPaint->rgbReserved, lpPaint->rgbReserved + 32, 0); _updatingRect = _updateRect; // Restrict drawing to the update area dc->setClipRect(_updateRect); if (_hFont) SelectObject(lpPaint->hdc, _hFont); return dc->m_hDC; } bool CWnd::EndPaint(const PAINTSTRUCT *lpPaint) { _updateRect = Common::Rect(); AfxGetApp()->afxUpdateWnds().remove(this); _updateErase = false; // If it's a persistent dc for the window, reset clipping if (_pDC) _pDC->resetClipRect(); return true; } CWnd *CWnd::GetDlgItem(int nID) const { return _children.contains(nID) ? _children[nID] : nullptr; } CWnd *CWnd::GetNextDlgGroupItem(CWnd *pWndCtl, bool bPrevious) const { // First set up the children hash map as a straight array // for easier iterating over Common::Array children; for (auto &child : _children) children.push_back(child._value); // Get the starting index int idStart = pWndCtl->GetDlgCtrlID(); int startIdx; for (startIdx = 0; startIdx < (int)children.size() && children[startIdx]->GetDlgCtrlID() != idStart; ++startIdx) { } assert(startIdx != (int)children.size()); // Remove any items from the array from the next WS_GROUP onwards const int style = WS_GROUP | WS_VISIBLE; int idx = startIdx + 1; for (; idx < (int)children.size(); ++idx) { if ((children[idx]->GetStyle() & style) == style) { // Found next group, remove remainder while (idx < (int)children.size()) children.remove_at(idx); } } // Remove any items before the start of the current group idx = startIdx; while (idx >= 0 && !(children[idx]->GetStyle() & WS_GROUP)) --idx; for (; idx > 0; --idx, --startIdx) children.remove_at(0); assert(children[startIdx] == pWndCtl); // Get the next item if (bPrevious) { if (--startIdx < 0) startIdx = (int)children.size() - 1; } else { if (++startIdx == (int)children.size()) startIdx = 0; } return children[startIdx]; } bool CWnd::GotoDlgCtrl(CWnd *pWndCtrl) { if (pWndCtrl != nullptr) { pWndCtrl->SetFocus(); // Give it focus SendMessage(WM_NEXTDLGCTL, (WPARAM)pWndCtrl->m_hWnd, true); // Update focus info return true; } return false; } bool CWnd::SubclassDlgItem(unsigned int nID, CWnd *pParent) { // Validate we're replacing the same kind of control assert(pParent->_children.contains(nID)); CWnd *oldControl = pParent->_children[nID]; assert(IsKindOf(oldControl->GetRuntimeClass())); // Remove the old control from the window pParent->_ownedControls.remove(oldControl); pParent->_children.erase(oldControl->_controlId); // Add the new control to the parent pParent->_children[nID] = this; m_pParentWnd = pParent; // Copy over the properties to the new control _windowText = oldControl->_windowText; _windowRect = oldControl->_windowRect; _controlId = oldControl->_controlId; m_nClassStyle = oldControl->m_nClassStyle; m_nStyle = oldControl->m_nStyle; _hFont = oldControl->_hFont; RECT screenRect = RectToRECT(0, 0, _windowRect.width(), _windowRect.height()); ClientToScreen(&screenRect); // Copy over the settings _hFont = oldControl->_hFont; _hPen = oldControl->_hPen; _hBrush = oldControl->_hBrush; _hPalette = oldControl->_hPalette; // Mark the control for being drawn AfxGetApp()->afxUpdateWnds().remove(oldControl); Invalidate(); return true; } bool CWnd::SetDlgItemText(int nIDDlgItem, const char *lpString) { CWnd *wnd = GetDlgItem(nIDDlgItem); if (wnd) { wnd->SetWindowText(lpString); return true; } return false; } int CWnd::GetDlgCtrlID() const { return _controlId; } void CWnd::CheckDlgButton(int nIDButton, unsigned int nCheck) { CWnd *btn = GetDlgItem(nIDButton); assert(btn); btn->SendMessage(BM_SETCHECK, nCheck); } LRESULT CWnd::SendDlgItemMessage(int nID, unsigned int message, WPARAM wParam, LPARAM lParam) const { CWnd *ctl = GetDlgItem(nID); assert(ctl); return ctl->SendMessage(message, wParam, lParam); } uintptr CWnd::SetTimer(uintptr nIDEvent, unsigned int nElapse, void (CALLBACK *lpfnTimer)(HWND, unsigned int, uintptr, uint32)) { return MFC::SetTimer(m_hWnd, nIDEvent, nElapse, lpfnTimer); } bool CWnd::KillTimer(uintptr nIDEvent) { return MFC::KillTimer(m_hWnd, nIDEvent); } void CWnd::createDialogIndirect(LPCDLGTEMPLATE dlgTemplate) { CDialog *dialog = dynamic_cast(this); assert(dialog); // Parse the template and use it to load controls Gfx::CDialogTemplate dt(dlgTemplate); dt.loadTemplate(dialog); SendMessage(WM_INITDIALOG); ShowWindow(SW_SHOWNORMAL); } void CWnd::SetCapture() { AfxGetApp()->SetCapture(m_hWnd); } void CWnd::ReleaseCapture() { AfxGetApp()->ReleaseCapture(); } void CWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { CWnd *pControl = GetDlgItem(nIDCtl); if (pControl) pControl->DrawItem(lpDrawItemStruct); } bool CWnd::OnCommand(WPARAM wParam, LPARAM lParam) { unsigned int nID = LOWORD(wParam); int nCode = HIWORD(wParam); // TODO: Original does some stuff with reflecting // the previous message to child control by default return OnCmdMsg(nID, nCode, nullptr, nullptr); } void CWnd::OnSetFont(HFONT hFont, bool bRedraw) { _hFont = hFont; if (bRedraw) Invalidate(); } bool CWnd::OnSetCursor(CWnd *pWnd, unsigned int nHitTest, unsigned int message) { MFC::SetCursor(MFC::LoadCursor(nullptr, IDC_ARROW)); return true; } void CWnd::SendMessageToDescendants(unsigned int message, WPARAM wParam, LPARAM lParam, bool bDeep, bool) { // Note: since in ScummVM the m_hWnd points // to the CWnd itself, all controls are currently // "permanent", hence the original bPermanent // param is meaningless Common::Queue queue; // Add immediate children for (auto &node : _children) queue.push(node._value); // Iterate through handling each child, and adding // in any child they have if doing a deep send while (!queue.empty()) { CWnd *wnd = queue.pop(); // If the control has any children, // add them if deep mode is flagged if (bDeep) { for (auto &node : wnd->_children) queue.push(node._value); } // Send the message to the control wnd->SendMessage(message, wParam, lParam); } } void CWnd::SendMessageToDescendants(HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam, bool bDeep, bool bOnlyPerm) { CWnd *wnd = CWnd::FromHandle(hWnd); wnd->SendMessageToDescendants(message, wParam, lParam, bDeep, bOnlyPerm); } bool CWnd::IsActiveWindow() const { return AfxGetApp()->GetActiveWindow() == this; } void CWnd::SetFont(CFont *pFont, bool bRedraw) { SendMessage(WM_SETFONT, (WPARAM)pFont->m_hObject, bRedraw); } void CWnd::pause() { AfxGetApp()->pause(); } void CWnd::OnShowWindow(bool bShow, unsigned int nStatus) { if (bShow) { // Invalidate this window Invalidate(); // Invalidate all descendants SendMessageToDescendants(WM_SETREDRAW, true); } } void CWnd::OnNcDestroy() { Detach(); ASSERT(m_hWnd == nullptr); } HWND CWnd::Detach() { HWND hWnd = m_hWnd; auto *pMap = AfxGetApp()->afxMapWnd(); assert(pMap); pMap->RemoveHandle(m_hWnd); m_hWnd = nullptr; return hWnd; } bool CWnd::OnQueryNewPalette() { return true; } } // namespace MFC } // namespace Bagel