#include "shellprv.h" #include "EBalloon.h" class CErrorBalloon { public: CErrorBalloon(); ~CErrorBalloon(); HRESULT ShowToolTip(HINSTANCE hInstance, HWND hwndTarget, const POINT *ppt, LPTSTR pszTitle, LPTSTR pszMessage, DWORD dwIconIndex, int iTimeout); void HideToolTip(BOOL fDestroy); protected: HWND _CreateToolTipWindow(HWND hwnd); static LRESULT CALLBACK _SubclassTipProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData); HWND _hwndTarget; // the targeted control hwnd HWND _hwndToolTip; // the tooltip control UINT_PTR _uTimerID; // the timer id }; #define ERRORBALLOONTIMERID 1000 #define EB_WARNINGBELOW 0x00000000 // default value. Balloon tooltips will be shown below the window by default. #define EB_WARNINGABOVE 0x00000004 // Ballon tooltips will be shown above the window by default. #define EB_WARNINGCENTERED 0x00000008 // Ballon tooltips will be shown pointing to the center of the window. CErrorBalloon::CErrorBalloon() { // our allocation function should have zeroed our memory. Check to make sure: ASSERT(0==_hwndToolTip); ASSERT(0==_uTimerID); } CErrorBalloon::~CErrorBalloon() { ASSERT(0==_hwndToolTip); ASSERT(0==_hwndTarget); } LRESULT CALLBACK CErrorBalloon::_SubclassTipProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData) { UNREFERENCED_PARAMETER(uID); CErrorBalloon * pthis = (CErrorBalloon*)dwRefData; switch (uMsg) { case WM_MOUSEACTIVATE: // Never activate tooltip pthis->HideToolTip(FALSE); return MA_NOACTIVATEANDEAT; case WM_DESTROY: pthis->HideToolTip(TRUE); delete pthis; break; case WM_TIMER: if (wParam == ERRORBALLOONTIMERID) { pthis->HideToolTip(FALSE); return 0; } break; default: break; } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } HRESULT CErrorBalloon::ShowToolTip(HINSTANCE hinst, HWND hwndTarget, const POINT *ppt, LPTSTR pszTitle, LPTSTR pszMessage, DWORD dwIconIndex, int iTimeout) { if (_hwndToolTip) { HideToolTip(FALSE); } HWND hwnd = _CreateToolTipWindow(hwndTarget); if (hwnd) { int x, y; x = ppt->x; y = ppt->y; SendMessage(hwnd, TTM_TRACKPOSITION, 0, MAKELONG(x,y)); if (pszTitle) { SendMessage(hwnd, TTM_SETTITLE, (WPARAM)dwIconIndex, (LPARAM)pszTitle); } TOOLINFO ti = {0}; ti.cbSize = TTTOOLINFOW_V2_SIZE; ti.hwnd = hwnd; ti.uId = 1; ti.lpszText = pszMessage; SendMessage(hwnd, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); // Show the tooltip SendMessage(hwnd, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); _uTimerID = SetTimer(hwnd, ERRORBALLOONTIMERID, iTimeout, NULL); if (SetWindowSubclass(hwnd, CErrorBalloon::_SubclassTipProc, (UINT_PTR)this, (LONG_PTR)this)) { _hwndToolTip = hwnd; return S_OK; } // we blew the subclassing DestroyWindow(hwnd); } return E_FAIL; } // CreateToolTipWindow // // Creates our tooltip control. We share this one tooltip control and use it for all invalid // input messages. The control is hiden when not in use and then shown when needed. // HWND CErrorBalloon::_CreateToolTipWindow(HWND hwndTarget) { HWND hwnd = CreateWindow( TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndTarget, NULL, GetModuleHandle(NULL), NULL); ASSERT(!_hwndToolTip); ASSERT(!_hwndTarget); if (hwnd) { TOOLINFO ti = {0}; ti.cbSize = TTTOOLINFOW_V2_SIZE; ti.uFlags = TTF_TRACK; ti.hwnd = hwnd; ti.uId = 1; // set the version so we can have non buggy mouse event forwarding SendMessage(hwnd, CCM_SETVERSION, COMCTL32_VERSION, 0); SendMessage(hwnd, TTM_ADDTOOL, 0, (LPARAM)&ti); SendMessage(hwnd, TTM_SETMAXTIPWIDTH, 0, 300); // set tink-tink? } return hwnd; } void CErrorBalloon::HideToolTip(BOOL fOnDestroy) { // When the timer fires we hide the tooltip window if (fOnDestroy) { // we need to tear everything down if (_uTimerID) { KillTimer(_hwndTarget, ERRORBALLOONTIMERID); _uTimerID = 0; } if (_hwndTarget) { // RemoveWindowSubclass(_hwndTarget, CErrorBalloon::_SubclassTargetProc, (UINT_PTR)this); RemoveProp(_hwndTarget, L"ShellConditionalBalloon"); _hwndTarget = NULL; } if (_hwndToolTip) { RemoveWindowSubclass(_hwndToolTip, CErrorBalloon::_SubclassTipProc, (UINT_PTR)this); SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, FALSE, 0); _hwndToolTip = NULL; } } else DestroyWindow(_hwndToolTip); } STDAPI SHShowConditionalBalloon(HWND hwnd, CBSHOW show, CONDITIONALBALLOON *pscb) { HRESULT hr = E_OUTOFMEMORY; if (hwnd) { CErrorBalloon *peb = (CErrorBalloon *) GetProp(hwnd, L"ShellConditionalBalloon"); if (show != CBSHOW_HIDE && pscb) { DWORD dw = 0; BOOL fShow = TRUE; HKEY hkSession = NULL; if (SUCCEEDED(SHCreateSessionKey(MAXIMUM_ALLOWED, &hkSession))) { fShow = (ERROR_SUCCESS != SHGetValue(hkSession, NULL, pscb->pszValue, NULL, NULL, NULL)); } // check cLimit if (fShow && pscb->cLimit) { ASSERT(pscb->hKey); DWORD cb = sizeof(dw); SHGetValue(pscb->hKey, pscb->pszSubKey, pscb->pszValue, NULL, &dw, &cb); fShow = dw < pscb->cLimit; } if (fShow) { // we need to show something if (!peb) { peb = new CErrorBalloon(); if (peb && !SetProp(hwnd, L"ShellConditionalBalloon", peb)) { delete peb; peb = NULL; } } if (peb) { TCHAR szTitle[MAX_PATH]; TCHAR szMessage[INFOTIPSIZE]; LoadString(pscb->hinst, pscb->idsTitle, szTitle, ARRAYSIZE(szTitle)); LoadString(pscb->hinst, pscb->idsMessage, szMessage, ARRAYSIZE(szMessage)); // Set the tooltip display point //if (pscb->pt.x == -1 && pscb->pt.y == -1) // _GetTipPoint(hwndTarget, &pscb->pt); DWORD dwMSecs = pscb->dwMSecs; if (dwMSecs == 0) { // default to 1 sec / 10 chars; dwMSecs = lstrlen(szMessage) * 100; if (dw == 0) dwMSecs *= 5; // first time put it up for a while } hr = peb->ShowToolTip(pscb->hinst, hwnd, &pscb->pt, szTitle, szMessage, pscb->ttiIcon, dwMSecs); if (FAILED(hr)) { RemoveProp(hwnd, L"ShellConditionalBalloon"); delete peb; } if (pscb->cLimit) { dw++; SHSetValueW(pscb->hKey, pscb->pszSubKey, pscb->pszValue, REG_DWORD, &dw, sizeof(dw)); } } } else hr = S_FALSE; if (hkSession) { SHSetValueW(hkSession, NULL, pscb->pszValue, REG_NONE, NULL, NULL); RegCloseKey(hkSession); } } else if (peb) { peb->HideToolTip(FALSE); // we delete ourselves during WM_DESTROY } } return hr; }