diff --git a/widget/src/windows/nsWindow.cpp b/widget/src/windows/nsWindow.cpp index fdd9eeff39da..ffebb2b8cfd0 100644 --- a/widget/src/windows/nsWindow.cpp +++ b/widget/src/windows/nsWindow.cpp @@ -257,6 +257,10 @@ PRInt32 GetWindowsVersion() return version; } + +// Pick some random timer ID. Is there a better way? +#define NS_FLASH_TIMER_ID 0x011231984 + static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID); static NS_DEFINE_IID(kRenderingContextCID, NS_RENDERING_CONTEXT_CID); @@ -424,6 +428,119 @@ static PRBool is_vk_down(int vk) #endif // #ifndef APPCOMMAND_BROWSER_BACKWARD +/* This object maintains a correlation between attention timers and the + windows to which they belong. It's lighter than a hashtable (expected usage + is really just one at a time) and allows nsWindow::GetNSWindowPtr + to remain private. */ +class nsAttentionTimerMonitor { +public: + nsAttentionTimerMonitor() : mHeadTimer(0) { } + ~nsAttentionTimerMonitor() { + TimerInfo *current, *next; + for (current = mHeadTimer; current; current = next) { + next = current->next; + delete current; + } + } + void AddTimer(HWND timerWindow, HWND flashWindow, PRInt32 maxFlashCount, UINT timerID) { + TimerInfo *info; + PRBool newInfo = PR_FALSE; + info = FindInfo(timerWindow); + if (!info) { + info = new TimerInfo; + newInfo = PR_TRUE; + } + if (info) { + info->timerWindow = timerWindow; + info->flashWindow = flashWindow; + info->maxFlashCount = maxFlashCount; + info->flashCount = 0; + info->timerID = timerID; + info->hasFlashed = PR_FALSE; + info->next = 0; + if (newInfo) + AppendTimer(info); + } + } + HWND GetFlashWindowFor(HWND timerWindow) { + TimerInfo *info = FindInfo(timerWindow); + return info ? info->flashWindow : 0; + } + PRInt32 GetMaxFlashCount(HWND timerWindow) { + TimerInfo *info = FindInfo(timerWindow); + return info ? info->maxFlashCount : -1; + } + PRInt32 GetFlashCount(HWND timerWindow) { + TimerInfo *info = FindInfo(timerWindow); + return info ? info->flashCount : -1; + } + void IncrementFlashCount(HWND timerWindow) { + TimerInfo *info = FindInfo(timerWindow); + ++(info->flashCount); + } + void KillTimer(HWND timerWindow) { + TimerInfo *info = FindInfo(timerWindow); + if (info) { + // make sure it's unflashed and kill the timer + + if (info->hasFlashed) + ::FlashWindow(info->flashWindow, FALSE); + + ::KillTimer(info->timerWindow, info->timerID); + RemoveTimer(info); + delete info; + } + } + void SetFlashed(HWND timerWindow) { + TimerInfo *info = FindInfo(timerWindow); + if (info) + info->hasFlashed = PR_TRUE; + } + +private: + struct TimerInfo { + HWND timerWindow, + flashWindow; + UINT timerID; + PRInt32 maxFlashCount; + PRInt32 flashCount; + PRBool hasFlashed; + TimerInfo *next; + }; + TimerInfo *FindInfo(HWND timerWindow) { + TimerInfo *scan; + for (scan = mHeadTimer; scan; scan = scan->next) + if (scan->timerWindow == timerWindow) + break; + return scan; + } + void AppendTimer(TimerInfo *info) { + if (!mHeadTimer) + mHeadTimer = info; + else { + TimerInfo *scan, *last; + for (scan = mHeadTimer; scan; scan = scan->next) + last = scan; + last->next = info; + } + } + void RemoveTimer(TimerInfo *info) { + TimerInfo *scan, *last = 0; + for (scan = mHeadTimer; scan && scan != info; scan = scan->next) + last = scan; + if (scan) { + if (last) + last->next = scan->next; + else + mHeadTimer = scan->next; + } + } + + TimerInfo *mHeadTimer; +}; + +static nsAttentionTimerMonitor *gAttentionTimerMonitor = 0; + HWND nsWindow::GetTopLevelHWND(HWND aWnd, PRBool aStopOnFirstTopLevel) { HWND curWnd = aWnd; @@ -1316,6 +1433,8 @@ NS_METHOD nsWindow::Destroy() if (mWnd) { // prevent the widget from causing additional events mEventCallback = nsnull; + if (gAttentionTimerMonitor) + gAttentionTimerMonitor->KillTimer(mWnd); // if IME is disabled, restore it. if (mOldIMC) { @@ -7340,6 +7459,38 @@ void nsWindow::GetCompositionWindowPos(HIMC hIMC, PRUint32 aEventType, COMPOSITI cpForm->rcArea.bottom = cpForm->ptCurrentPos.y + event.theReply.mCursorPosition.height; } +// This function is called on a timer to do the flashing. It simply toggles the flash +// status until the window comes to the foreground. +static VOID CALLBACK nsGetAttentionTimerFunc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) +{ + // flash the window until we're in the foreground. + if (::GetForegroundWindow() != hwnd) + { + // flash the outermost owner + HWND flashwnd = gAttentionTimerMonitor->GetFlashWindowFor(hwnd); + + PRInt32 maxFlashCount = gAttentionTimerMonitor->GetMaxFlashCount(hwnd); + PRInt32 flashCount = gAttentionTimerMonitor->GetFlashCount(hwnd); + if (maxFlashCount > 0) { + // We have a max flash count, if we haven't met it yet, flash again. + if (flashCount < maxFlashCount) { + ::FlashWindow(flashwnd, TRUE); + gAttentionTimerMonitor->IncrementFlashCount(hwnd); + } + else + gAttentionTimerMonitor->KillTimer(hwnd); + } + else { + // The caller didn't specify a flash count. + ::FlashWindow(flashwnd, TRUE); + } + + gAttentionTimerMonitor->SetFlashed(hwnd); + } + else + gAttentionTimerMonitor->KillTimer(hwnd); +} + // Draw user's attention to this window until it comes to foreground. NS_IMETHODIMP nsWindow::GetAttention(PRInt32 aCycleCount) @@ -7348,24 +7499,28 @@ nsWindow::GetAttention(PRInt32 aCycleCount) if (!mWnd) return NS_ERROR_NOT_INITIALIZED; - // Don't flash if the flash count is 0 or if the - // top level window is already active. - if (aCycleCount == 0 || ::GetForegroundWindow == GetTopLevelHWND(mWnd)) + // Don't flash if the flash count is 0. + if (aCycleCount == 0) return NS_OK; - DWORD defaultCycleCount = 0; - ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0); - HWND flashWnd = mWnd; - while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) - flashWnd = ownerWnd; + // timer is on the parentmost window; window to flash is its ownermost + HWND timerwnd = GetTopLevelHWND(mWnd); + HWND flashwnd = timerwnd; + HWND nextwnd; + while ((nextwnd = ::GetWindow(flashwnd, GW_OWNER)) != 0) + flashwnd = nextwnd; + + // If window is in foreground, no notification is necessary. + if (::GetForegroundWindow() != timerwnd) { + // kick off a timer that does single flash until the window comes to the foreground + if (!gAttentionTimerMonitor) + gAttentionTimerMonitor = new nsAttentionTimerMonitor; + if (gAttentionTimerMonitor) { + gAttentionTimerMonitor->AddTimer(timerwnd, flashwnd, aCycleCount, NS_FLASH_TIMER_ID); + ::SetTimer(timerwnd, NS_FLASH_TIMER_ID, GetCaretBlinkTime(), (TIMERPROC)nsGetAttentionTimerFunc); + } + } - FLASHWINFO flashInfo; - ZeroMemory(&flashInfo, sizeof(FLASHWINFO)); - flashInfo.cbSize = sizeof(FLASHWINFO); - flashInfo.hwnd = flashWnd; - flashInfo.dwFlags = FLASHW_ALL; - flashInfo.uCount = aCycleCount > 0 ? aCycleCount : defaultCycleCount; - ::FlashWindowEx(&flashInfo); return NS_OK; }