#include "StdAfx.h" #include "CalculatorWindow.h" //#include "DateFormat.h" #include "VisualStyle.h" #include "Utils.h" #include "Calculator.h" #include "Settings.h" //////////////////////////////////////////////////////////////////////////////// // const UINT_PTR UPDATE_TIMER_ID = 1; WNDPROC wpOrigEditProc; LRESULT CALLBACK WndEditProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam); //////////////////////////////////////////////////////////////////////////////// // class SelectGdiObject { public: SelectGdiObject(HDC hDC, HGDIOBJ hGdiObj) : m_hDC(hDC), m_hGdiObj(hGdiObj), m_hSaveGdiObj(::SelectObject(hDC, hGdiObj)) { ATLASSERT(m_hSaveGdiObj && m_hSaveGdiObj != HGDI_ERROR); } ~SelectGdiObject() { if(m_hSaveGdiObj && m_hSaveGdiObj != HGDI_ERROR) ::SelectObject(m_hDC, m_hSaveGdiObj); } operator HGDIOBJ() const { return m_hGdiObj; } private: HDC m_hDC; HGDIOBJ m_hGdiObj; HGDIOBJ m_hSaveGdiObj; private: // not copyable SelectGdiObject(const SelectGdiObject&); SelectGdiObject& operator=(const SelectGdiObject&); }; class DoubleBufferPaint { public: DoubleBufferPaint(HWND hWnd, DWORD dwRop = SRCCOPY) : m_hWnd(hWnd), m_dwRop(dwRop) { m_hdc = ::BeginPaint(m_hWnd, &m_ps); int x, y, cx, cy; InitDims(&x, &y, &cx, &cy); m_hdcMem = ::CreateCompatibleDC(m_hdc); m_hbmMem = ::CreateCompatibleBitmap(m_hdc, cx, cy); m_hbmSave = ::SelectObject(m_hdcMem, m_hbmMem); ::SetWindowOrgEx(m_hdcMem, x, y, NULL); } ~DoubleBufferPaint() { int x, y, cx, cy; InitDims(&x, &y, &cx, &cy); ::BitBlt(m_hdc, x, y, cx, cy, m_hdcMem, x, y, m_dwRop); ::SelectObject(m_hdcMem, m_hbmSave); ::DeleteObject(m_hbmMem); ::DeleteDC(m_hdcMem); ::EndPaint(m_hWnd, &m_ps); } HDC GetDC() const { return m_hdcMem; } const RECT& GetPaintRect() const { return m_ps.rcPaint; } private: void InitDims(int* px, int* py, int* pcx, int* pcy) const { *px = m_ps.rcPaint.left; *py = m_ps.rcPaint.top; *pcx = m_ps.rcPaint.right - m_ps.rcPaint.left; *pcy = m_ps.rcPaint.bottom - m_ps.rcPaint.top; } private: HWND m_hWnd; DWORD m_dwRop; HDC m_hdc; HDC m_hdcMem; HGDIOBJ m_hbmMem; HGDIOBJ m_hbmSave; PAINTSTRUCT m_ps; private: // not copyable DoubleBufferPaint(const DoubleBufferPaint&); DoubleBufferPaint& operator=(const DoubleBufferPaint&); }; //////////////////////////////////////////////////////////////////////////////// // CCalculatorWindow CCalculatorWindow::CCalculatorWindow() : m_fHasFocus(FALSE), m_ptrVisualStyle(CVisualStyle::Create()), m_calc() { // ::GetLocalTime(&m_stLocalTime); // m_unformatInputString[0] = '\0'; // m_unformatOutputString[0] = '\0'; m_numDecimalDigit = 2; ReadRegionalSettings(); } CCalculatorWindow::~CCalculatorWindow() { } //////////////////////////////////////////////////////////////////////////////// // BOOL CCalculatorWindow::CreateStandalone() { RECT rc = { 0, 0, 220, 48 }; // Top-level popup, always-on-top, tool window (no taskbar/Alt-Tab button). // Created with the full "active" frame (caption + sizing border) so the // window is resizable from the first show; ApplyFrameStyle strips it back // to a clean look when the window is inactive. m_captionVisible (true) // matches this initial style. HWND hwnd = __super::Create(NULL, rc, L"Taskbar Calculator", WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME, WS_EX_TOPMOST | WS_EX_TOOLWINDOW); if (!hwnd) return FALSE; m_hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), TEXT("0"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | ES_RIGHT, 0, 0, 0, 0, m_hWnd, NULL, _AtlBaseModule.GetModuleInstance(), 0); wpOrigEditProc = (WNDPROC)::SetWindowLongPtr(m_hwndEdit, GWLP_WNDPROC, (LONG_PTR)WndEditProc); if (wpOrigEditProc == 0) return FALSE; ::SetWindowLongPtr(m_hwndEdit, GWLP_USERDATA, (LONG_PTR)this); // Build the font and size the window for this window's DPI. RebuildForDpi(::GetDpiForWindow(m_hWnd)); PlaceInitial(); return TRUE; } void CCalculatorWindow::RebuildForDpi(UINT dpi) { m_dpi = dpi; // Rebuild the themed font/colors for this DPI. m_ptrVisualStyle.Free(); m_ptrVisualStyle.Attach(CVisualStyle::Create(CVisualStyle::Auto, dpi)); if (::IsWindow(m_hwndEdit)) SetWindowFont(m_hwndEdit, m_ptrVisualStyle->GetFont(), TRUE); // Size the CLIENT area from the (now DPI-scaled) font, scaling the fixed // paddings/minimums by the same DPI, then adjust for the current frame. POINTL ideal = CalcIdealSize(); // Width: a saved custom width (normalized to 96 DPI) scaled to this DPI, // otherwise the default auto-fit width. Never below the 5-digit minimum. const LONG minClientW = MinClientWidth(dpi); LONG clientW; DWORD w96; if (Settings::LoadWindowWidth(w96)) clientW = ::MulDiv((LONG)w96, (int)dpi, 96); else clientW = ideal.x + ::MulDiv(24, (int)dpi, 96); if (clientW < minClientW) clientW = minClientW; LONG clientH = ideal.y + ::MulDiv(8, (int)dpi, 96); LONG minH = ::MulDiv(28, (int)dpi, 96); if (clientH < minH) clientH = minH; RECT r = { 0, 0, clientW, clientH }; ::AdjustWindowRectExForDpi(&r, (DWORD)GetWindowLongPtr(GWL_STYLE), FALSE, (DWORD)GetWindowLongPtr(GWL_EXSTYLE), dpi); SetWindowPos(NULL, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); } void CCalculatorWindow::PlaceInitial() { RECT wr; GetWindowRect(&wr); int w = wr.right - wr.left, h = wr.bottom - wr.top; POINT pt; if (Settings::LoadWindowPos(pt)) { // Saved point is canonical (client origin). Convert to the window // origin for the style currently in effect. RECT r = { pt.x, pt.y, pt.x + 1, pt.y + 1 }; AdjustWindowRectEx(&r, (DWORD)GetWindowLongPtr(GWL_STYLE), FALSE, (DWORD)GetWindowLongPtr(GWL_EXSTYLE)); // Only restore the saved position if it still lands on a connected // monitor. Otherwise (e.g. the window was left on an external display // that is no longer attached) the point is off-screen and the window // would be invisible; fall through to first-run anchoring instead. RECT full = { r.left, r.top, r.left + w, r.top + h }; if (::MonitorFromRect(&full, MONITOR_DEFAULTTONULL) != NULL) { SetWindowPos(NULL, r.left, r.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); ClampToWorkArea(); // pull it out of the taskbar if the saved pos overlapped return; } } // First run: anchor bottom-right, just above the notification area. APPBARDATA abd{}; abd.cbSize = sizeof(abd); RECT work; SystemParametersInfo(SPI_GETWORKAREA, 0, &work, 0); int x = work.right - w - 8; int y = work.bottom - h - 8; if (SHAppBarMessage(ABM_GETTASKBARPOS, &abd)) { if (abd.rc.top > work.top) y = abd.rc.top - h - 8; } SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } void CCalculatorWindow::ClampToWorkArea() { RECT wr; GetWindowRect(&wr); const int w = wr.right - wr.left; const int h = wr.bottom - wr.top; MONITORINFO mi = { sizeof(mi) }; if (!::GetMonitorInfo(::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST), &mi)) return; const RECT& work = mi.rcWork; // Keep the whole window inside the work area (which excludes the taskbar). // Push in from the right/bottom first, then clamp to the top/left so a // window larger than the work area still anchors at its top-left corner. int x = wr.left, y = wr.top; if (x + w > work.right) x = work.right - w; if (y + h > work.bottom) y = work.bottom - h; if (x < work.left) x = work.left; if (y < work.top) y = work.top; if (x != wr.left || y != wr.top) SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } void CCalculatorWindow::ToggleVisible() { if (m_shown) { ShowWindow(SW_HIDE); m_shown = false; } else { SetWindowPos(HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); SetForegroundWindow(m_hWnd); ::SetFocus(m_hwndEdit); m_shown = true; } Settings::SaveVisible(m_shown); } POINTL CCalculatorWindow::CalcMinimalSize() const { POINTL pt = { ::GetSystemMetrics(SM_CXMIN), ::GetSystemMetrics(SM_CYMIN) }; return pt; } SIZE CCalculatorWindow::MeasureString(LPCTSTR text) const { SIZE size = { 0 }; HDC hic = ::CreateIC(_T("DISPLAY"), NULL, NULL, NULL); { SelectGdiObject gdiFont(hic, m_ptrVisualStyle->GetFont()); const BOOL bRes = ::GetTextExtentPoint32(hic, text, (int)_tcslen(text), &size); ATLASSERT(bRes); } ::DeleteDC(hic); return size; } POINTL CCalculatorWindow::CalcIdealSize() const { if(!IsWindow()) return CalcMinimalSize(); const SIZE size = MeasureString(TEXT("99.999,99")); const POINTL pt = { size.cx, size.cy }; return pt; } LONG CCalculatorWindow::MinClientWidth(UINT dpi) const { // Worst-case 5 digits: sign + thousands separator + five digits. const SIZE size = MeasureString(TEXT("-99.999")); return size.cx + ::MulDiv(24, (int)dpi, 96); } BOOL CCalculatorWindow::HasFocus() const { return m_fHasFocus; } //////////////////////////////////////////////////////////////////////////////// // Message handlers LRESULT CCalculatorWindow::OnFocus( UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { m_fHasFocus = (uMsg == WM_SETFOCUS); if ( m_fHasFocus ) ReadRegionalSettings(); return 0L; } LRESULT CCalculatorWindow::OnDestroy( UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { KillTimer(UPDATE_TIMER_ID); if(::IsWindow(m_hwndEdit)) { ::SetWindowLongPtr(m_hwndEdit, GWLP_WNDPROC, (LONG_PTR)wpOrigEditProc); ::DestroyWindow(m_hwndEdit); } return 0; } LRESULT CCalculatorWindow::OnEraseBackground( UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { return 1; } LRESULT CCalculatorWindow::OnPaint( UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { DoubleBufferPaint dbuffPaint(m_hWnd); //Paint(dbuffPaint.GetDC(), dbuffPaint.GetPaintRect()); m_ptrVisualStyle->DrawBackground(m_hWnd, dbuffPaint.GetDC(), dbuffPaint.GetPaintRect()); ::SetBkMode(dbuffPaint.GetDC(), TRANSPARENT); RECT rc; GetClientRect(&rc); #if _DEBUG CString str; str.Format(_T("RECT (TOP: %ld, LEFT: %ld, BOTTOM: %ld, RIGHT: %ld)\n"), rc.top, rc.left, rc.bottom, rc.right); OutputDebugString(str); #endif //#ifdef BIGFONT // const UINT defaultHeight = 40; //#else // const UINT defaultHeight = 22; //#endif ::SetWindowPos(m_hwndEdit, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW | SWP_NOZORDER); return 0; } LRESULT CCalculatorWindow::OnPowerBroadcast( UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { if(wParam == PBT_APMRESUMECRITICAL || wParam == PBT_APMRESUMESUSPEND || wParam == PBT_APMRESUMESTANDBY) { // re-enable timer //ATLVERIFY(SetUpdateTimer() != 0); } return 0; } LRESULT CCalculatorWindow::OnThemeChanged( UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { // re-create theme style at the current DPI (also refreshes the edit font // and size). RebuildForDpi(m_dpi); return 0; } LRESULT CCalculatorWindow::OnDpiChanged( UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { const UINT dpi = HIWORD(wParam); RebuildForDpi(dpi); // recreate font + resize for the new DPI // Move to the position Windows suggests for the new monitor, keeping the // size we just computed from the DPI-scaled font. const RECT* prc = (const RECT*)lParam; RECT wr; GetWindowRect(&wr); SetWindowPos(NULL, prc->left, prc->top, wr.right - wr.left, wr.bottom - wr.top, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } LRESULT CCalculatorWindow::OnClose(UINT, WPARAM, LPARAM, BOOL&) { // Hide to tray instead of destroying. ShowWindow(SW_HIDE); m_shown = false; Settings::SaveVisible(false); return 0; } LRESULT CCalculatorWindow::OnLButtonDown(UINT, WPARAM, LPARAM, BOOL&) { // Allow dragging the window by its body. ReleaseCapture(); SendMessage(WM_NCLBUTTONDOWN, HTCAPTION, 0); return 0; } LRESULT CCalculatorWindow::OnExitSizeMove(UINT, WPARAM, LPARAM, BOOL&) { // Keep the window within the monitor work area so it never sits over (and // then behind) the taskbar. ClampToWorkArea(); // Persist the canonical position (client origin) and, only if this was an // actual resize, the width (normalized to 96 DPI so it scales // proportionally on other monitors). A plain move keeps the auto-fit width. RECT rc; GetClientRect(&rc); if (m_didResize) Settings::SaveWindowWidth((DWORD)::MulDiv(rc.right - rc.left, 96, (int)m_dpi)); ::MapWindowPoints(m_hWnd, NULL, (LPPOINT)&rc, 2); Settings::SaveWindowPos(POINT{ rc.left, rc.top }); return 0; } LRESULT CCalculatorWindow::OnEnterSizeMove(UINT, WPARAM, LPARAM, BOOL&) { // Capture the values the interactive-resize handler needs. Height is // locked; width cannot go below the 5-digit minimum for the active frame. m_didResize = false; RECT wr; GetWindowRect(&wr); m_fixedFrameHeight = wr.bottom - wr.top; RECT r = { 0, 0, MinClientWidth(m_dpi), 100 }; ::AdjustWindowRectExForDpi(&r, (DWORD)GetWindowLongPtr(GWL_STYLE), FALSE, (DWORD)GetWindowLongPtr(GWL_EXSTYLE), m_dpi); m_minResizeWidth = r.right - r.left; return 0; } LRESULT CCalculatorWindow::OnSizing(UINT, WPARAM wParam, LPARAM lParam, BOOL&) { RECT* rc = (RECT*)lParam; m_didResize = true; // Horizontal only: keep the height fixed. rc->bottom = rc->top + m_fixedFrameHeight; // Enforce the minimum width by adjusting whichever edge is being dragged. if ((rc->right - rc->left) < m_minResizeWidth) { switch (wParam) { case WMSZ_LEFT: case WMSZ_TOPLEFT: case WMSZ_BOTTOMLEFT: rc->left = rc->right - m_minResizeWidth; break; default: rc->right = rc->left + m_minResizeWidth; break; } } return TRUE; } LRESULT CCalculatorWindow::OnActivate(UINT, WPARAM wParam, LPARAM, BOOL& bHandled) { // Caption + close button only while the window is active. ApplyFrameStyle(LOWORD(wParam) != WA_INACTIVE); bHandled = FALSE; // let default activation processing run too return 0; } void CCalculatorWindow::ApplyFrameStyle(bool visible) { if (visible == m_captionVisible) return; m_captionVisible = visible; // The client rect must not move on screen: capture it before the style change. RECT rc; GetClientRect(&rc); ::MapWindowPoints(m_hWnd, NULL, (LPPOINT)&rc, 2); LONG_PTR style = GetWindowLongPtr(GWL_STYLE); if (visible) style |= (WS_CAPTION | WS_SYSMENU | WS_THICKFRAME); else style &= ~(WS_CAPTION | WS_SYSMENU | WS_THICKFRAME); SetWindowLongPtr(GWL_STYLE, style); RECT r = rc; AdjustWindowRectExForDpi(&r, (DWORD)style, FALSE, (DWORD)GetWindowLongPtr(GWL_EXSTYLE), m_dpi); // Guard: never push the top edge above the work area of the window's monitor. MONITORINFO mi = { sizeof(mi) }; if (::GetMonitorInfo(::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST), &mi) && r.top < mi.rcWork.top) ::OffsetRect(&r, 0, mi.rcWork.top - r.top); SetWindowPos(NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); } void CCalculatorWindow::Paint(HDC hdc, const RECT& rcPaint) const { m_ptrVisualStyle->DrawBackground(m_hWnd, hdc, rcPaint); RECT rcClient = { 0 }; GetClientRect(&rcClient); SelectGdiObject gdiFont(hdc, m_ptrVisualStyle->GetFont()); ::SetBkMode(hdc, TRANSPARENT); ::SetTextColor(hdc, m_ptrVisualStyle->GetTextColor()); // ::DrawText(hdc, m_sDateString, m_sDateString.GetLength(), &rcClient, DT_CENTER | DT_SINGLELINE | DT_VCENTER); } LRESULT CCalculatorWindow::OnEditKeyDown(HWND hWnd, WPARAM wParam, LPARAM /*lParam*/) { TCHAR c = '0'; bool bChangeState = true; switch (wParam) { case VK_NUMPAD0: c = CALC_0; break; case VK_NUMPAD1: c = CALC_1; break; case VK_NUMPAD2: c = CALC_2; break; case VK_NUMPAD3: c = CALC_3; break; case VK_NUMPAD4: c = CALC_4; break; case VK_NUMPAD5: c = CALC_5; break; case VK_NUMPAD6: c = CALC_6; break; case VK_NUMPAD7: c = CALC_7; break; case VK_NUMPAD8: c = CALC_8; break; case VK_NUMPAD9: c = CALC_9; break; case VK_RETURN: c = CALC_TOT; break; case VK_MULTIPLY: c = CALC_MUL; break; case VK_ADD: c = CALC_ADD; break; case VK_SUBTRACT: c = CALC_SUB; break; case VK_DECIMAL: c = CALC_P; break; case VK_DIVIDE: c = CALC_DIV; break; case VK_ESCAPE: c = CALC_RESET; break; case VK_DELETE: case VK_LEFT: case VK_RIGHT: return 0; case VK_BACK: { UINT len = m_unformatOutputString.GetLength(); if (len > 1) m_unformatOutputString.Delete(len - 1, 1); else m_unformatOutputString = CALC_STR_0; } c = '_'; break; default: bChangeState = false; } if (bChangeState) ProcessChar(hWnd, c); return (bChangeState ? 0 : 1); } LRESULT CCalculatorWindow::OnEditChar(HWND hWnd, WPARAM wParam, LPARAM /*lParam*/) { TCHAR c = '0'; bool bChangeState = true; switch (wParam) { case CALC_0: case CALC_1: case CALC_2: case CALC_3: case CALC_4: case CALC_5: case CALC_6: case CALC_7: case CALC_8: case CALC_9: case CALC_TOT: case CALC_RESET: case CALC_MUL: case CALC_ADD: case CALC_SUB: case CALC_P: case CALC_DIV: case CALC_SQRT: case CALC_000: case CALC_NEG: case CALC_PERC: case CALC_I: c = (TCHAR)wParam; break; case 'z': c = CALC_000; break; case 'n': c = CALC_RESET; break; case 'q': c = CALC_SQRT; break; case 'i': c = CALC_I; break; case 'p': c = CALC_NEG; break; default: bChangeState = false; } if (bChangeState) ProcessChar(hWnd, c); return (bChangeState ? 0 : 1); } void CCalculatorWindow::ProcessChar(HWND hWnd, TCHAR c) { //char oldString[1024]; TCHAR newString[1024]; m_unformatInputString = m_unformatOutputString; LPTSTR unformatOutputString = m_unformatOutputString.GetBuffer(1024); m_calc.change_state(c, m_unformatInputString.GetString(), unformatOutputString); m_unformatOutputString.ReleaseBuffer(-1); if (m_unformatOutputString != ERR_STR_OVERFLOW && m_unformatOutputString != ERR_STR_DIVBYZERO) { FormatString(m_unformatOutputString, newString); } else lstrcpy(newString, m_unformatOutputString); ::SetWindowText(hWnd, (LPCTSTR)newString); int l = lstrlen(newString); ::SendMessage(hWnd, EM_SETSEL, l, l); } bool CCalculatorWindow::FormatString(LPCTSTR strInput, LPTSTR strOutput) { _tcscpy_s(strOutput, 1024, strInput); { LPTSTR cDecSep = _tcschr(strOutput, CALC_P); if (cDecSep != NULL) *cDecSep = m_cDecSepStandard; _tcsrev(strOutput); size_t len = _tcslen(strOutput); TCHAR strBuffer[1024]; INT y = -1; UINT j = 0; UINT numDecimalDigit = 0; bool bStartFormatting = false; if (cDecSep == NULL) { bStartFormatting = true; y = 0; } for (UINT i = 0; iOnEditKeyDown(hWnd, wParam, lParam) == 0) { bKeyAlreadyProcessd = true; return 0; } else bKeyAlreadyProcessd = false; break; case WM_CHAR: if (bKeyAlreadyProcessd) return 0; if (pThis->OnEditChar(hWnd, wParam, lParam) == 0) return 0; break; case WM_SETFOCUS: ::HideCaret(hWnd); //pThis->OnSetFocus(); break; case WM_KILLFOCUS: //pThis->OnKillFocus(); break; case WM_SETTINGCHANGE: pThis->ReadRegionalSettings(); pThis->ProcessChar(hWnd, '_'); break; } return ::CallWindowProc(wpOrigEditProc, hWnd, uMessage, wParam, lParam); }