home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Open Source / AutoHotKey / Source / AutoHotkey104705_source.exe / source / WinGroup.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-06-19  |  25.2 KB  |  550 lines

  1. /*
  2. AutoHotkey
  3.  
  4. Copyright 2003-2007 Chris Mallett (support@autohotkey.com)
  5.  
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15. */
  16.  
  17. #include "stdafx.h" // pre-compiled headers
  18. #include "WinGroup.h"
  19. #include "window.h" // for several lower level window functions
  20. #include "globaldata.h" // for DoWinDelay
  21. #include "application.h" // for DoWinDelay's MsgSleep()
  22.  
  23. // Define static members data:
  24. WinGroup *WinGroup::sGroupLastUsed = NULL;
  25. HWND *WinGroup::sAlreadyVisited = NULL;
  26. int WinGroup::sAlreadyVisitedCount = 0;
  27.  
  28.  
  29. ResultType WinGroup::AddWindow(char *aTitle, char *aText, Label *aJumpToLabel, char *aExcludeTitle, char *aExcludeText)
  30. // Caller should ensure that at least one param isn't NULL/blank.
  31. // GroupActivate will tell its caller to jump to aJumpToLabel if a WindowSpec isn't found.
  32. // This function is not thread-safe because it adds an entry to the list of window specs.
  33. // In addition, if this function is being called by one thread while another thread is calling IsMember(),
  34. // the thread-safety notes in IsMember() apply.
  35. {
  36.     // v1.0.41: If a window group can ever be deleted (or its window specs), that might defeat the
  37.     // thread-safety of WinExist/WinActive.
  38.     // v1.0.36.05: If all four window parameters are blank, allow it to be added but provide
  39.     // a non-blank ExcludeTitle so that the window-finding routines won't see it as the
  40.     // "last found window".  99.99% of the time, it is undesirable to have Program Manager
  41.     // in a window group of any kind, so that is used as the placeholder:
  42.     if (!(*aTitle || *aText || *aExcludeTitle || *aExcludeText))
  43.         aExcludeTitle = "Program Manager";
  44.  
  45.     // Though the documentation is clear on this, some users will still probably execute
  46.     // each GroupAdd statement more than once.  Thus, to prevent more and more memory
  47.     // from being allocated for duplicates, do not add the window specification if it
  48.     // already exists in the group:
  49.     if (mFirstWindow) // Traverse the circular linked-list to look for a match.
  50.         for (WindowSpec *win = mFirstWindow
  51.             ; win != NULL; win = (win->mNextWindow == mFirstWindow) ? NULL : win->mNextWindow)
  52.             if (!strcmp(win->mTitle, aTitle) && !strcmp(win->mText, aText) // All are case sensitive.
  53.                 && !strcmp(win->mExcludeTitle, aExcludeTitle) && !strcmp(win->mExcludeText, aExcludeText))
  54.                 return OK;
  55.  
  56.     // SimpleHeap::Malloc() will set these new vars to the constant empty string if their
  57.     // corresponding params are blank:
  58.     char *new_title, *new_text, *new_exclude_title, *new_exclude_text;
  59.     if (!(new_title = SimpleHeap::Malloc(aTitle))) return FAIL; // It already displayed the error for us.
  60.     if (!(new_text = SimpleHeap::Malloc(aText)))return FAIL;
  61.     if (!(new_exclude_title = SimpleHeap::Malloc(aExcludeTitle))) return FAIL;
  62.     if (!(new_exclude_text = SimpleHeap::Malloc(aExcludeText)))   return FAIL;
  63.  
  64.     // The precise method by which the follows steps are done should be thread-safe even if
  65.     // some other thread calls IsMember() in the middle of the operation.  But any changes
  66.     // must be carefully reviewed:
  67.     WindowSpec *the_new_win = new WindowSpec(new_title, new_text, aJumpToLabel, new_exclude_title, new_exclude_text);
  68.     if (the_new_win == NULL)
  69.         return g_script.ScriptError(ERR_OUTOFMEM);
  70.     if (mFirstWindow == NULL)
  71.         mFirstWindow = the_new_win;
  72.     else
  73.         mLastWindow->mNextWindow = the_new_win; // Formerly it pointed to First, so nothing is lost here.
  74.     // This must be done after the above:
  75.     mLastWindow = the_new_win;
  76.     // Make it circular: Last always points to First.  It's okay if it points to itself:
  77.     mLastWindow->mNextWindow = mFirstWindow;
  78.     ++mWindowCount;
  79.     return OK;
  80. }
  81.  
  82.  
  83.  
  84. ResultType WinGroup::ActUponAll(ActionTypeType aActionType, int aTimeToWaitForClose)
  85. {
  86.     if (IsEmpty())
  87.         return OK;  // OK since this is the expected behavior in this case.
  88.     // Don't need to call Update() in this case.
  89.     WindowSearch ws;
  90.     ws.mFirstWinSpec = mFirstWindow; // Act upon all windows that match any WindowSpec in the group.
  91.     ws.mActionType = aActionType;    // Set the type of action to be performed on each window.
  92.     ws.mTimeToWaitForClose = aTimeToWaitForClose;  // Only relevant for WinClose and WinKill.
  93.     EnumWindows(EnumParentActUponAll, (LPARAM)&ws);
  94.     if (ws.mFoundParent) // It acted upon least one window.
  95.         DoWinDelay;
  96.     return OK;
  97. }
  98.  
  99.  
  100.  
  101. ResultType WinGroup::CloseAndGoToNext(bool aStartWithMostRecent)
  102. // If the foreground window is a member of this group, close it and activate
  103. // the next member.
  104. {
  105.     if (IsEmpty())
  106.         return OK;  // OK since this is the expected behavior in this case.
  107.     // Otherwise:
  108.     // Don't call Update(), let (De)Activate() do that.
  109.  
  110.     HWND fore_win = GetForegroundWindow();
  111.     // Even if it's NULL, don't return since the legacy behavior is to continue on to the final part below.
  112.  
  113.     WindowSpec *win_spec = IsMember(fore_win, g);
  114.     if (   (mIsModeActivate && win_spec) || (!mIsModeActivate && !win_spec)   )
  115.     {
  116.         // If the user is using a GroupActivate hotkey, we don't want to close
  117.         // the foreground window if it's not a member of the group.  Conversely,
  118.         // if the user is using GroupDeactivate, we don't want to close a
  119.         // member of the group.  This precaution helps prevent accidental closing
  120.         // of windows that suddenly pop up to the foreground just as you've
  121.         // realized (too late) that you pressed the "close" hotkey.
  122.         // MS Visual Studio/C++ gets messed up when it is directly sent a WM_CLOSE,
  123.         // probably because the wrong window (it has two mains) is being sent the close.
  124.         // But since that's the only app I've ever found that doesn't work right,
  125.         // it seems best not to change our close method just for it because sending
  126.         // keys is a fairly high overhead operation, and not without some risk due to
  127.         // not knowing exactly what keys the user may have physically held down.
  128.         // Also, we'd have to make this module dependent on the keyboard module,
  129.         // which would be another drawback.
  130.         // Try to wait for it to close, otherwise the same window may be activated
  131.         // again before it has been destroyed, defeating the purpose of the
  132.         // "ActivateNext" part of this function's job:
  133.         // SendKeys("!{F4}");
  134.         if (fore_win)
  135.         {
  136.             WinClose(fore_win, 500);
  137.             DoWinDelay;
  138.         }
  139.     }
  140.     //else do the activation below anyway, even though no close was done.
  141.     return mIsModeActivate ? Activate(aStartWithMostRecent, win_spec) : Deactivate(aStartWithMostRecent);
  142. }
  143.  
  144.  
  145.  
  146. ResultType WinGroup::Activate(bool aStartWithMostRecent, WindowSpec *aWinSpec, Label **aJumpToLabel)
  147. {
  148.     if (aJumpToLabel) // Initialize early in case of early return.
  149.         *aJumpToLabel = NULL;
  150.     if (IsEmpty())
  151.         return OK;  // OK since this is the expected behavior in this case.
  152.     // Otherwise:
  153.     if (!Update(true)) // Update our private member vars.
  154.         return FAIL;  // It already displayed the error for us.
  155.     WindowSpec *win, *win_to_activate_next = aWinSpec;
  156.     bool group_is_active = false; // Set default.
  157.     HWND activate_win, active_window = GetForegroundWindow(); // This value is used in more than one place.
  158.     if (win_to_activate_next)
  159.     {
  160.         // The caller told us which WindowSpec to start off trying to activate.
  161.         // If the foreground window matches that WindowSpec, do nothing except
  162.         // marking it as visited, because we want to stay on this window under
  163.         // the assumption that it was newly revealed due to a window on top
  164.         // of it having just been closed:
  165.         if (win_to_activate_next == IsMember(active_window, g))
  166.         {
  167.             group_is_active = true;
  168.             MarkAsVisited(active_window);
  169.             return OK;
  170.         }
  171.         // else don't mark as visited even if it's a member of the group because
  172.         // we're about to attempt to activate a different window: the next
  173.         // unvisited member of this same WindowSpec.  If the below doesn't
  174.         // find any of those, it continue on through the list normally.
  175.     }
  176.     else // Caller didn't tell us which, so determine it.
  177.     {
  178.         if (win_to_activate_next = IsMember(active_window, g)) // Foreground window is a member of this group.
  179.         {
  180.             // Set it to activate this same WindowSpec again in case there's
  181.             // more than one that matches (e.g. multiple notepads).  But first,
  182.             // mark the current window as having been visited if it hasn't
  183.             // already by marked by a prior iteration.  Update: This method
  184.             // doesn't work because if a unvisted matching window became the
  185.             // foreground window by means other than using GroupActivate
  186.             // (e.g. launching a new instance of the app: now there's another
  187.             // matching window in the foreground).  So just call it straight
  188.             // out.  It has built-in dupe-checking which should prevent the
  189.             // list from filling up with dupes if there are any special
  190.             // situations in which that might otherwise happen:
  191.             //if (!sAlreadyVisitedCount)
  192.             group_is_active = true;
  193.             MarkAsVisited(active_window);
  194.         }
  195.         else // It's not a member.
  196.         {
  197.             win_to_activate_next = mFirstWindow;  // We're starting fresh, so start at the first window.
  198.             // Reset the list of visited windows:
  199.             sAlreadyVisitedCount = 0;
  200.         }
  201.     }
  202.  
  203.     // Activate any unvisited window that matches the win_to_activate_next spec.
  204.     // If none, activate the next window spec in the series that does have an
  205.     // existing window:
  206.     // If the spec we're starting at already has some windows marked as visited,
  207.     // set this variable so that we know to retry the first spec again in case
  208.     // a full circuit is made through the window specs without finding a window
  209.     // to activate.  Note: Using >1 vs. >0 might protect against any infinite-loop
  210.     // conditions that may be lurking:
  211.     bool retry_starting_win_spec = (sAlreadyVisitedCount > 1);
  212.     bool retry_is_in_effect = false;
  213.     for (win = win_to_activate_next;;)
  214.     {
  215.         // Call this in the mode to find the last match, which  makes things nicer
  216.         // because when the sequence wraps around to the beginning, the windows will
  217.         // occur in the same order that they did the first time, rather than going
  218.         // backwards through the sequence (which is counterintuitive for the user):
  219.         if (   activate_win = WinActivate(g, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText
  220.             // This next line is whether to find last or first match.  We always find the oldest
  221.             // (bottommost) match except when the user has specifically asked to start with the
  222.             // most recent.  But it only makes sense to start with the most recent if the
  223.             // group isn't currently active (i.e. we're starting fresh), because otherwise
  224.             // windows would be activated in an order different from what was already shown
  225.             // the first time through the enumeration, which doesn't seem to be ever desirable:
  226.             , !aStartWithMostRecent || group_is_active
  227.             , sAlreadyVisited, sAlreadyVisitedCount)   )
  228.         {
  229.             // We found a window to activate, so we're done.
  230.             // Probably best to do this before WinDelay in case another hotkey fires during the delay:
  231.             MarkAsVisited(activate_win);
  232.             DoWinDelay;
  233.             //MsgBox(win->mText, 0, win->mTitle);
  234.             break;
  235.         }
  236.         // Otherwise, no window was found to activate.
  237.         if (aJumpToLabel && win->mJumpToLabel && !sAlreadyVisitedCount)
  238.         {
  239.             // Caller asked us to return in this case, so that it can
  240.             // use this value to execute a user-specified Gosub:
  241.             *aJumpToLabel = win->mJumpToLabel;  // Set output param for the caller.
  242.             return OK;
  243.         }
  244.         if (retry_is_in_effect)
  245.             // This was the final attempt because we've already gone all the
  246.             // way around the circular linked list of WindowSpecs.  This check
  247.             // must be done, otherwise an infinite loop might result if the windows
  248.             // that formed the basis for determining the value of
  249.             // retry_starting_win_spec have since been destroyed:
  250.             break;
  251.         // Otherwise, go onto the next one in the group:
  252.         win = win->mNextWindow;
  253.         // Even if the above didn't change the value of <win> (because there's only
  254.         // one WinSpec in the list), it's still correct to reset this count because
  255.         // we want to start the fresh again after all the windows have been
  256.         // visited.  Note: The only purpose of sAlreadyVisitedCount as used by
  257.         // this function is to indicate which windows in a given WindowSpec have
  258.         // been visited, not which windows altogether (i.e. it's not necessary to
  259.         // remember which windows have been visited once we move on to a new
  260.         // WindowSpec).
  261.         sAlreadyVisitedCount = 0;
  262.         if (win == win_to_activate_next)
  263.         {
  264.             // We've made one full circuit of the circular linked list without
  265.             // finding an existing window to activate. At this point, the user
  266.             // has pressed a hotkey to do a GroupActivate, but nothing has happened
  267.             // yet.  We always want something to happen unless there's absolutely
  268.             // no existing windows to activate, or there's only a single window in
  269.             // the system that matches the group and it's already active.
  270.             if (retry_starting_win_spec)
  271.             {
  272.                 // Mark the foreground window as visited so that it won't be
  273.                 // mistakenly activated again by the next iteration:
  274.                 MarkAsVisited(active_window);
  275.                 retry_is_in_effect = true;
  276.                 // Now continue with the next iteration of the loop so that it
  277.                 // will activate a different instance of this WindowSpec rather
  278.                 // than getting stuck on this one.
  279.             }
  280.             else
  281.                 break;
  282.         }
  283.     }
  284.     return OK;
  285. }
  286.  
  287.  
  288.  
  289. ResultType WinGroup::Deactivate(bool aStartWithMostRecent)
  290. {
  291.     if (IsEmpty())
  292.         return OK;  // OK since this is the expected behavior in this case.
  293.     // Otherwise:
  294.     if (!Update(false)) // Update our private member vars.
  295.         return FAIL;  // It already displayed the error for us.
  296.  
  297.     HWND active_window = GetForegroundWindow();
  298.     if (IsMember(active_window, g))
  299.         sAlreadyVisitedCount = 0;
  300.  
  301.     // Activate the next unvisited non-member:
  302.     WindowSearch ws;
  303.     ws.mFindLastMatch = !aStartWithMostRecent || sAlreadyVisitedCount;
  304.     ws.mAlreadyVisited = sAlreadyVisited;
  305.     ws.mAlreadyVisitedCount = sAlreadyVisitedCount;
  306.     ws.mFirstWinSpec = mFirstWindow;
  307.  
  308.     EnumWindows(EnumParentFindAnyExcept, (LPARAM)&ws);
  309.  
  310.     if (ws.mFoundParent)
  311.     {
  312.         // If the window we're about to activate owns other visble parent windows, it can
  313.         // never truly be activated because it must always be below them in the z-order.
  314.         // Thus, instead of activating it, activate the first (and usually the only?)
  315.         // visible window that it owns.  Doing this makes things nicer for some apps that
  316.         // have a pair of main windows, such as MS Visual Studio (and probably many more),
  317.         // because it avoids activating such apps twice in a row as the user progresses
  318.         // through the sequence:
  319.         HWND first_visible_owned = WindowOwnsOthers(ws.mFoundParent);
  320.         if (first_visible_owned)
  321.         {
  322.             MarkAsVisited(ws.mFoundParent);  // Must mark owner as well as the owned window.
  323.             // Activate the owned window instead of the owner because it usually
  324.             // (probably always, given the comments above) is the real main window:
  325.             ws.mFoundParent = first_visible_owned;
  326.         }
  327.         SetForegroundWindowEx(ws.mFoundParent);
  328.         // Probably best to do this before WinDelay in case another hotkey fires during the delay:
  329.         MarkAsVisited(ws.mFoundParent);
  330.         DoWinDelay;
  331.     }
  332.     else // No window was found to activate (they have all been visited).
  333.     {
  334.         if (sAlreadyVisitedCount)
  335.         {
  336.             bool wrap_around = (sAlreadyVisitedCount > 1);
  337.             sAlreadyVisitedCount = 0;
  338.             if (wrap_around)
  339.             {
  340.                 // The user pressed a hotkey to do something, yet nothing has happened yet.
  341.                 // We want something to happen every time if there's a qualifying
  342.                 // "something" that we can do.  And in this case there is: we can start
  343.                 // over again through the list, excluding the foreground window (which
  344.                 // the user has already had a chance to review):
  345.                 MarkAsVisited(active_window);
  346.                 // Make a recursive call to self.  This can't result in an infinite
  347.                 // recursion (stack fault) because the called layer will only
  348.                 // recurse a second time if sAlreadyVisitedCount > 1, which is
  349.                 // impossible with the current logic:
  350.                 Deactivate(false); // Seems best to ignore aStartWithMostRecent in this case?
  351.             }
  352.         }
  353.     }
  354.     // Even if a window wasn't found, we've done our job so return OK:
  355.     return OK;
  356. }
  357.  
  358.  
  359.  
  360. inline ResultType WinGroup::Update(bool aIsModeActivate)
  361. {
  362.     mIsModeActivate = aIsModeActivate;
  363.     if (sGroupLastUsed != this)
  364.     {
  365.         sGroupLastUsed = this;
  366.         sAlreadyVisitedCount = 0; // Since it's a new group, reset the array to start fresh.
  367.     }
  368.     if (!sAlreadyVisited) // Allocate the array on first use.
  369.         // Getting it from SimpleHeap reduces overhead for the avg. case (i.e. the first
  370.         // block of SimpleHeap is usually never fully used, and this array won't even
  371.         // be allocated for short scripts that don't even using window groups.
  372.         if (   !(sAlreadyVisited = (HWND *)SimpleHeap::Malloc(MAX_ALREADY_VISITED * sizeof(HWND)))   )
  373.             return FAIL;  // It already displayed the error for us.
  374.     return OK;
  375. }
  376.  
  377.  
  378.  
  379. WindowSpec *WinGroup::IsMember(HWND aWnd, global_struct &aSettings)
  380. // Thread-safety: This function is thread-safe even when the main thread happens to be calling AddWindow()
  381. // and changing the linked list while it's being traversed here by the hook thread.  However, any subsequent
  382. // changes to this function or AddWindow() must be carefully reviewed.
  383. // Although our caller may be a WindowSearch method, and thus we might make
  384. // a recursive call back to that same method, things have been reviewed to ensure that
  385. // thread-safety is maintained, even if the calling thread is the hook.
  386. {
  387.     if (!aWnd)
  388.         return NULL;  // Some callers on this.
  389.     WindowSearch ws;
  390.     ws.SetCandidate(aWnd);
  391.     for (WindowSpec *win = mFirstWindow; win != NULL;)  // v1.0.41: "win != NULL" was added for thread-safety.
  392.     {
  393.         if (ws.SetCriteria(aSettings, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText) && ws.IsMatch())
  394.             return win;
  395.         // Otherwise, no match, so go onto the next one:
  396.         win = win->mNextWindow;
  397.         if (!win || win == mFirstWindow) // v1.0.41: The check of !win was added for thread-safety.
  398.             // We've made one full circuit of the circular linked list,
  399.             // discovering that the foreground window isn't a member
  400.             // of the group:
  401.             break;
  402.     }
  403.     return NULL;  // Because it would have returned already if a match was found.
  404. }
  405.  
  406.  
  407. /////////////////////////////////////////////////////////////////////////
  408.  
  409.  
  410. BOOL CALLBACK EnumParentFindAnyExcept(HWND aWnd, LPARAM lParam)
  411. // Find the first parent window that doesn't match any of the WindowSpecs in
  412. // the linked list, and that hasn't already been visited.
  413. {
  414.     // Since the following two sections apply only to GroupDeactivate (since that's our only caller),
  415.     // they both seem okay even in light of the ahk_group method.
  416.  
  417.     if (!IsWindowVisible(aWnd))
  418.         // Skip these because we alwayswant them to stay invisible, regardless
  419.         // of the setting for g.DetectHiddenWindows:
  420.         return TRUE;
  421.  
  422.     // UPDATE: Because the window of class Shell_TrayWnd (the taskbar) is also always-on-top,
  423.     // the below prevents it from ever being activated too, which is almost always desirable.
  424.     // However, this prevents the addition of WS_DISABLED as an extra criteria for skipping
  425.     // a window.  Maybe that's best for backward compatibility anyway.
  426.     // Skip always-on-top windows, such as SplashText, because probably shouldn't
  427.     // be activated (especially in this mode, which is often used to visit the user's
  428.     // "non-favorite" windows).  In addition, they're already visible so the user already
  429.     // knows about them, so there's no need to have them presented for review.
  430.     if (GetWindowLong(aWnd, GWL_EXSTYLE) & WS_EX_TOPMOST)
  431.         return TRUE;
  432.  
  433.     // It probably would have been better to use the class name (ProgMan?) for this instead,
  434.     // but there is doubt that the same class name is used across all OSes.  The reason for
  435.     // doing that is to avoid ambiguity with other windows that just happen to be called
  436.     // "Program Manager".  See similar section in EnumParentActUponAll().
  437.     // Skip "Program Manager" too because activating it would serve no purpose.  This is probably
  438.     // the same HWND that GetShellWindow() returns, but GetShellWindow() isn't supported on
  439.     // Win9x or WinNT, so don't bother using it.  And GetDeskTopWindow() apparently doesn't
  440.     // return "Program Manager" (something with a blank title I think):
  441.     char win_title[20]; // Just need enough size to check for Program Manager
  442.     if (GetWindowText(aWnd, win_title, sizeof(win_title)) && !stricmp(win_title, "Program Manager"))
  443.         return TRUE;
  444.  
  445.     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  446.     ws.SetCandidate(aWnd);
  447.  
  448.     // Check this window's attributes against each set of criteria present in the group.  If
  449.     // it's a match for any set of criteria, it's a member of the group and thus should be
  450.     // excluded since we want only NON-members:
  451.     for (WindowSpec *win = ws.mFirstWinSpec;;)
  452.     {
  453.         // For each window in the linked list, check if aWnd is a match for it:
  454.         if (ws.SetCriteria(g, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText) && ws.IsMatch(true))
  455.             // Match found, so aWnd is a member of the group. But we want to find non-members only,
  456.             // so keep searching:
  457.             return TRUE;
  458.         // Otherwise, no match, but keep checking until aWnd has been compared against
  459.         // all the WindowSpecs in the group:
  460.         win = win->mNextWindow;
  461.         if (win == ws.mFirstWinSpec)
  462.         {
  463.             // We've made one full circuit of the circular linked list without
  464.             // finding a match.  So aWnd is the one we're looking for unless
  465.             // it's in the list of exceptions:
  466.             for (int i = 0; i < ws.mAlreadyVisitedCount; ++i)
  467.                 if (aWnd == ws.mAlreadyVisited[i])
  468.                     return TRUE; // It's an exception, so keep searching.
  469.             // Otherwise, this window meets the criteria, so return it to the caller and
  470.             // stop the enumeration.  UPDATE: Rather than stopping the enumeration,
  471.             // continue on through all windows so that the last match is found.
  472.             // That makes things nicer because when the sequence wraps around to the
  473.             // beginning, the windows will occur in the same order that they did
  474.             // the first time, rather than going backwards through the sequence
  475.             // (which is counterintuitive for the user):
  476.             ws.mFoundParent = aWnd; // No need to increment ws.mFoundCount in this case.
  477.             return ws.mFindLastMatch;
  478.         }
  479.     } // The loop above is infinite unless a "return" is encountered inside.
  480. }
  481.  
  482.  
  483.  
  484. BOOL CALLBACK EnumParentActUponAll(HWND aWnd, LPARAM lParam)
  485. // Caller must have ensured that lParam isn't NULL and that it contains a non-NULL mFirstWinSpec.
  486. {
  487.     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  488.  
  489.     // Skip windows the command isn't supposed to detect.  ACT_WINSHOW is exempt because
  490.     // hidden windows are always detected by the WinShow command:
  491.     if (!(g.DetectHiddenWindows || ws.mActionType == ACT_WINSHOW || IsWindowVisible(aWnd)))
  492.         return TRUE;
  493.  
  494.     int nCmdShow;
  495.     ws.SetCandidate(aWnd);
  496.  
  497.     for (WindowSpec *win = ws.mFirstWinSpec;;)
  498.     {
  499.         // For each window in the linked list, check if aWnd is a match for it:
  500.         if (ws.SetCriteria(g, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText) && ws.IsMatch())
  501.         {
  502.             // Match found, so aWnd is a member of the group.  In addition, IsMatch() has set
  503.             // the value of ws.mFoundParent to tell our caller that at least one window was acted upon.
  504.             // See Line::PerformShowWindow() for comments about the following section.
  505.             nCmdShow = SW_NONE; // Set default each time.
  506.             switch (ws.mActionType)
  507.             {
  508.             case ACT_WINCLOSE:
  509.             case ACT_WINKILL:
  510.                 // mTimeToWaitForClose is not done in a very efficient way here: to keep code size
  511.                 // in check, it closes each window individually rather than sending WM_CLOSE to all
  512.                 // the windows simultaneously and then waiting until all have vanished:
  513.                 WinClose(aWnd, ws.mTimeToWaitForClose, ws.mActionType == ACT_WINKILL); // DoWinDelay is done by our caller.
  514.                 return TRUE; // All done with the current window, so fetch the next one.
  515.  
  516.             case ACT_WINMINIMIZE:
  517.                 if (IsWindowHung(aWnd))
  518.                 {
  519.                     if (g_os.IsWin2000orLater())
  520.                         nCmdShow = SW_FORCEMINIMIZE;
  521.                 }
  522.                 else
  523.                     nCmdShow = SW_MINIMIZE;
  524.                 break;
  525.  
  526.             case ACT_WINMAXIMIZE: if (!IsWindowHung(aWnd)) nCmdShow = SW_MAXIMIZE; break;
  527.             case ACT_WINRESTORE:  if (!IsWindowHung(aWnd)) nCmdShow = SW_RESTORE;  break;
  528.             case ACT_WINHIDE: nCmdShow = SW_HIDE; break;
  529.             case ACT_WINSHOW: nCmdShow = SW_SHOW; break;
  530.             }
  531.  
  532.             if (nCmdShow != SW_NONE)
  533.                 ShowWindow(aWnd, nCmdShow);
  534.                 // DoWinDelay is not done here because our caller will do it once only, which
  535.                 // seems best when there are a lot of windows being acted upon here.
  536.  
  537.             // Now that this matching window has been acted upon (or avoided due to being hung),
  538.             // continue the enumeration to get the next candidate window:
  539.             return TRUE;
  540.         }
  541.         // Otherwise, no match, keep checking until aWnd has been compared against all the WindowSpecs in the group:
  542.         win = win->mNextWindow;
  543.         if (win == ws.mFirstWinSpec)
  544.             // We've made one full circuit of the circular linked list without
  545.             // finding a match, so aWnd is not a member of the group and
  546.             // should not be closed.
  547.             return TRUE; // Continue the enumeration.
  548.     }
  549. }
  550.