--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -252,16 +252,20 @@ PRInt32 GetWindowsVersion()
osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
// This cast is safe and supposed to be here, don't worry
::GetVersionEx((OSVERSIONINFO*)&osInfo);
version = (osInfo.dwMajorVersion & 0xff) << 8 | (osInfo.dwMinorVersion & 0xff);
}
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);
// When we build we are currently (11/27/01) setting the WINVER to 0x0400
// Which means we do not compile in the system resource for the HAND cursor
// this enables us still define the resource and if it isn't there then we will
// get our own hand cursor.
// 32649 is the resource number as defined by WINUSER.H for this cursor
@@ -419,16 +423,129 @@ static PRBool is_vk_down(int vk)
#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))
//#define GET_DEVICE_LPARAM(lParam) ((WORD)(HIWORD(lParam) & FAPPCOMMAND_MASK))
//#define GET_MOUSEORKEY_LPARAM GET_DEVICE_LPARAM
//#define GET_FLAGS_LPARAM(lParam) (LOWORD(lParam))
//#define GET_KEYSTATE_LPARAM(lParam) GET_FLAGS_LPARAM(lParam)
#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;
HWND topWnd = NULL;
while (curWnd)
{
topWnd = curWnd;
@@ -1311,16 +1428,18 @@ NS_METHOD nsWindow::Destroy()
}
EnableDragDrop(PR_FALSE);
// destroy the HWND
if (mWnd) {
// prevent the widget from causing additional events
mEventCallback = nsnull;
+ if (gAttentionTimerMonitor)
+ gAttentionTimerMonitor->KillTimer(mWnd);
// if IME is disabled, restore it.
if (mOldIMC) {
mOldIMC = ::ImmAssociateContext(mWnd, mOldIMC);
NS_ASSERTION(!mOldIMC, "Another IMC was associated");
}
HICON icon;
@@ -7347,42 +7466,78 @@ void nsWindow::GetCompositionWindowPos(H
cpForm->ptCurrentPos.y = event.theReply.mCursorPosition.y + IME_Y_OFFSET +
event.theReply.mCursorPosition.height;
cpForm->rcArea.left = cpForm->ptCurrentPos.x;
cpForm->rcArea.top = cpForm->ptCurrentPos.y;
cpForm->rcArea.right = cpForm->ptCurrentPos.x + event.theReply.mCursorPosition.width;
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)
{
// Got window?
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;
-
- 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);
+ // 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);
+ }
+ }
+
return NS_OK;
}
NS_IMETHODIMP
nsWindow::GetLastInputEventTime(PRUint32& aTime)
{
WORD qstatus = HIWORD(GetQueueStatus(QS_INPUT));