Backed out bug 313403. (merge)
authorVladimir Vukicevic <vladimir@pobox.com>
Wed, 20 Aug 2008 13:28:49 -0700
changeset 17164 73967cc9e4ee07b66a5a3f9a35958027a51507d5
parent 17162 30da9cae7cf2aa0445c3c7cdcae0aeea0b5710f1 (current diff)
parent 17163 2ee05d1a0f59ff18a6d6f2f6dc0ee9982676ce3b (diff)
child 17165 7e6f8b885113878dc00f2e6527776bc1eb14e527
child 18320 74a9a3453bd9aac11a14e83d8065c647b28f6b77
child 18521 f197b51bbc29a30860e750ee87fd0a047a024f2e
push idunknown
push userunknown
push dateunknown
bugs313403
milestone1.9.1a2pre
Backed out bug 313403. (merge)
widget/src/windows/nsWindow.cpp
--- 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));