Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1352 lines
41 KiB

  1. // Display the Progress Dialog for the progress on the completion of some
  2. // generic operation. This is most often used for Deleting, Uploading, Copying,
  3. // Moving and Downloading large numbers of files.
  4. #include "priv.h"
  5. #include "resource.h"
  6. #include "mluisupp.h"
  7. // this is how long we wait for the UI thread to create the progress hwnd before giving up
  8. #define WAIT_PROGRESS_HWND 10*1000 // ten seconds
  9. STDAPI CProgressDialog_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi);
  10. class CProgressDialog
  11. : public IProgressDialog
  12. , public IOleWindow
  13. , public IActionProgressDialog
  14. , public IActionProgress
  15. , public IObjectWithSite
  16. {
  17. public:
  18. CProgressDialog();
  19. // IUnknown
  20. STDMETHODIMP_(ULONG) AddRef(void);
  21. STDMETHODIMP_(ULONG) Release(void);
  22. STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
  23. // IProgressDialog
  24. STDMETHODIMP StartProgressDialog(HWND hwndParent, IUnknown * punkEnableModless, DWORD dwFlags, LPCVOID pvResevered);
  25. STDMETHODIMP StopProgressDialog(void);
  26. STDMETHODIMP SetTitle(LPCWSTR pwzTitle);
  27. STDMETHODIMP SetAnimation(HINSTANCE hInstAnimation, UINT idAnimation);
  28. STDMETHODIMP_(BOOL) HasUserCancelled(void);
  29. STDMETHODIMP SetProgress(DWORD dwCompleted, DWORD dwTotal);
  30. STDMETHODIMP SetProgress64(ULONGLONG ullCompleted, ULONGLONG ullTotal);
  31. STDMETHODIMP SetLine(DWORD dwLineNum, LPCWSTR pwzString, BOOL fCompactPath, LPCVOID pvResevered);
  32. STDMETHODIMP SetCancelMsg(LPCWSTR pwzCancelMsg, LPCVOID pvResevered);
  33. STDMETHODIMP Timer(DWORD dwAction, LPCVOID pvResevered);
  34. // IOleWindow
  35. STDMETHODIMP GetWindow(HWND * phwnd);
  36. STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; }
  37. // IActionProgressDialog
  38. STDMETHODIMP Initialize(SPINITF flags, LPCWSTR pszTitle, LPCWSTR pszCancel);
  39. STDMETHODIMP Stop();
  40. // IActionProgress
  41. STDMETHODIMP Begin(SPACTION action, SPBEGINF flags);
  42. STDMETHODIMP UpdateProgress(ULONGLONG ulCompleted, ULONGLONG ulTotal);
  43. STDMETHODIMP UpdateText(SPTEXT sptext, LPCWSTR pszText, BOOL fMayCompact);
  44. STDMETHODIMP QueryCancel(BOOL * pfCancelled);
  45. STDMETHODIMP ResetCancel();
  46. STDMETHODIMP End();
  47. // IObjectWithSite
  48. STDMETHODIMP SetSite(IUnknown *punk) { IUnknown_Set(&_punkSite, punk); return S_OK; }
  49. STDMETHODIMP GetSite(REFIID riid, void **ppv) { *ppv = 0; return _punkSite ? _punkSite->QueryInterface(riid, ppv) : E_FAIL;}
  50. // Other Public Methods
  51. static INT_PTR CALLBACK ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
  52. static DWORD CALLBACK ThreadProc(LPVOID pvThis) { return ((CProgressDialog *) pvThis)->_ThreadProc(); };
  53. static DWORD CALLBACK SyncThreadProc(LPVOID pvThis) { return ((CProgressDialog *) pvThis)->_SyncThreadProc(); };
  54. private:
  55. ~CProgressDialog(void);
  56. LONG _cRef;
  57. // State Accessible thru IProgressDialog
  58. LPWSTR _pwzTitle; // This will be used to cache the value passed to IProgressDialog::SetTitle() until the dialog is displayed
  59. UINT _idAnimation;
  60. HINSTANCE _hInstAnimation;
  61. LPWSTR _pwzLine1; // NOTE:
  62. LPWSTR _pwzLine2; // these are only used to init the dialog, otherwise, we just
  63. LPWSTR _pwzLine3; // call through on the main thread to update the dialog directly.
  64. LPWSTR _pwzCancelMsg; // If the user cancels, Line 1 & 2 will be cleared and Line 3 will get this msg.
  65. // Other internal state.
  66. HWND _hwndDlgParent; // parent window for message boxes
  67. HWND _hwndProgress; // dialog/progress window
  68. DWORD _dwFirstShowTime; // tick count when the dialog was first shown (needed so we don't flash it up for an instant)
  69. SPINITF _spinitf;
  70. SPBEGINF _spbeginf;
  71. IUnknown *_punkSite;
  72. HINSTANCE _hinstFree;
  73. BOOL _fCompletedChanged; // has the _dwCompleted changed since last time?
  74. BOOL _fTotalChanged; // has the _dwTotal changed since last time?
  75. BOOL _fChangePosted; // is there a change pending?
  76. BOOL _fCancel;
  77. BOOL _fTermThread;
  78. BOOL _fThreadRunning;
  79. BOOL _fInAction;
  80. BOOL _fMinimized;
  81. BOOL _fScaleBug; // Comctl32's PBM_SETRANGE32 msg will still cast it to an (int), so don't let the high bit be set.
  82. BOOL _fNoTime;
  83. BOOL _fReleaseSelf;
  84. BOOL _fInitialized;
  85. // Progress Values and Calculations
  86. DWORD _dwCompleted; // progress completed
  87. DWORD _dwTotal; // total progress
  88. DWORD _dwPrevRate; // previous progress rate (used for computing time remaining)
  89. DWORD _dwPrevTickCount; // the tick count when we last updated the progress time
  90. DWORD _dwPrevCompleted; // the ammount we had completed when we last updated the progress time
  91. DWORD _dwLastUpdatedTimeRemaining;// tick count when we last update the "Time remaining" field, we only update it every 5 seconds
  92. DWORD _dwLastUpdatedTickCount; // tick count when SetProgress was last called, used to calculate the rate
  93. UINT _iNumTimesSetProgressCalled;// how many times has the user called SetProgress?
  94. // Private Member Functions
  95. DWORD _ThreadProc(void);
  96. DWORD _SyncThreadProc(void);
  97. BOOL _OnInit(HWND hDlg);
  98. BOOL _ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
  99. void _PauseAnimation(BOOL bStop);
  100. void _UpdateProgressDialog(void);
  101. void _AsyncUpdate(void);
  102. HRESULT _SetProgressTime(void);
  103. void _SetProgressTimeEst(DWORD dwSecondsLeft);
  104. void _UserCancelled(void);
  105. HRESULT _DisplayDialog(void);
  106. void _CompactProgressPath(LPCWSTR pwzStrIn, BOOL fCompactPath, UINT idDlgItem, LPWSTR pwzStrOut, DWORD cchSize);
  107. HRESULT _SetLineHelper(LPCWSTR pwzNew, LPWSTR * ppwzDest, UINT idDlgItem, BOOL fCompactPath);
  108. HRESULT _SetTitleBarProgress(DWORD dwCompleted, DWORD dwTotal);
  109. HRESULT _BeginAction(SPBEGINF flags);
  110. void _SetModeless(BOOL fModeless);
  111. void _ShowProgressBar(HWND hwnd);
  112. };
  113. //#define TF_PROGRESS 0xFFFFFFFF
  114. #define TF_PROGRESS 0x00000000
  115. // REVIEW, we should tune this size down as small as we can
  116. // to get smoother multitasking (without effecting performance)
  117. #define MIN_MINTIME4FEEDBACK 5 // is it worth showing estimated time to completion feedback?
  118. #define MS_TIMESLICE 2000 // ms, (MUST be > 1000!) first average time to completion estimate
  119. #define SHOW_PROGRESS_TIMEOUT 1000 // 1 second
  120. #define MINSHOWTIME 2000 // 2 seconds
  121. // progress dialog message
  122. #define PDM_SHUTDOWN WM_APP
  123. #define PDM_TERMTHREAD (WM_APP + 1)
  124. #define PDM_UPDATE (WM_APP + 2)
  125. // progress dialog timer messages
  126. #define ID_SHOWTIMER 1
  127. #ifndef UNICODE
  128. #error "This code will only compile UNICODE for perf reasons. If you really need an ANSI browseui, write all the code to convert."
  129. #endif // !UNICODE
  130. // compacts path strings to fit into the Text1 and Text2 fields
  131. void CProgressDialog::_CompactProgressPath(LPCWSTR pwzStrIn, BOOL fCompactPath, UINT idDlgItem, LPWSTR pwzStrOut, DWORD cchSize)
  132. {
  133. WCHAR wzFinalPath[MAX_PATH];
  134. LPWSTR pwzPathToUse = (LPWSTR)pwzStrIn;
  135. // We don't compact the path if the dialog isn't displayed yet.
  136. if (fCompactPath && _hwndProgress)
  137. {
  138. RECT rc;
  139. int cxWidth;
  140. StrCpyNW(wzFinalPath, (pwzStrIn ? pwzStrIn : L""), ARRAYSIZE(wzFinalPath));
  141. // get the size of the text boxes
  142. HWND hwnd = GetDlgItem(_hwndProgress, idDlgItem);
  143. if (EVAL(hwnd))
  144. {
  145. HDC hdc;
  146. HFONT hfont;
  147. HFONT hfontSave;
  148. hdc = GetDC(_hwndProgress);
  149. hfont = (HFONT)SendMessage(_hwndProgress, WM_GETFONT, 0, 0);
  150. hfontSave = (HFONT)SelectObject(hdc, hfont);
  151. GetWindowRect(hwnd, &rc);
  152. cxWidth = rc.right - rc.left;
  153. ASSERT(cxWidth >= 0);
  154. PathCompactPathW(hdc, wzFinalPath, cxWidth);
  155. SelectObject(hdc, hfontSave);
  156. ReleaseDC(_hwndProgress, hdc);
  157. }
  158. pwzPathToUse = wzFinalPath;
  159. }
  160. StrCpyNW(pwzStrOut, (pwzPathToUse ? pwzPathToUse : L""), cchSize);
  161. }
  162. HRESULT CProgressDialog::_SetLineHelper(LPCWSTR pwzNew, LPWSTR * ppwzDest, UINT idDlgItem, BOOL fCompactPath)
  163. {
  164. WCHAR wzFinalPath[MAX_PATH];
  165. _CompactProgressPath(pwzNew, fCompactPath, idDlgItem, wzFinalPath, ARRAYSIZE(wzFinalPath));
  166. Str_SetPtrW(ppwzDest, wzFinalPath); // No, so cache the value for later.
  167. // Does the dialog exist?
  168. if (_hwndProgress)
  169. SetDlgItemText(_hwndProgress, idDlgItem, wzFinalPath);
  170. return S_OK;
  171. }
  172. HRESULT CProgressDialog::_DisplayDialog(void)
  173. {
  174. TraceMsg(TF_PROGRESS, "CProgressDialog::_DisplayDialog()");
  175. // Don't force ourselves into the foreground if a window we parented is already in the foreground:
  176. // This is part of the fix for NT bug 298163 (the confirm replace dialog was deactivated
  177. // by the progress dialog)
  178. HWND hwndCurrent = GetForegroundWindow();
  179. BOOL fChildIsForeground = FALSE;
  180. while (NULL != (hwndCurrent = GetParent(hwndCurrent)))
  181. {
  182. if (_hwndProgress == hwndCurrent)
  183. {
  184. fChildIsForeground = TRUE;
  185. break;
  186. }
  187. }
  188. if (fChildIsForeground)
  189. {
  190. ShowWindow(_hwndProgress, SW_SHOWNOACTIVATE);
  191. }
  192. else
  193. {
  194. ShowWindow(_hwndProgress, SW_SHOW);
  195. SetForegroundWindow(_hwndProgress);
  196. }
  197. SetFocus(GetDlgItem(_hwndProgress, IDCANCEL));
  198. return S_OK;
  199. }
  200. DWORD CProgressDialog::_SyncThreadProc()
  201. {
  202. _InitComCtl32(); // Get ready for the Native Font Control
  203. _hwndProgress = CreateDialogParam(MLGetHinst(), MAKEINTRESOURCE(DLG_PROGRESSDIALOG),
  204. _hwndDlgParent, ProgressDialogProc, (LPARAM)this);
  205. _fThreadRunning = (_hwndProgress != NULL);
  206. return TRUE;
  207. }
  208. DWORD CProgressDialog::_ThreadProc(void)
  209. {
  210. if (_hwndProgress)
  211. {
  212. // WARNING - copy perf goes way down if this is normal or
  213. // better priority. the default thread pri should be low.
  214. // however if there are situations in which it should be higher,
  215. // we can add SPBEGINF bits to support it.
  216. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
  217. SetTimer(_hwndProgress, ID_SHOWTIMER, SHOW_PROGRESS_TIMEOUT, NULL);
  218. // Did if finish while we slept?
  219. if (!_fTermThread)
  220. {
  221. // No, so display the dialog.
  222. MSG msg;
  223. while(GetMessage(&msg, NULL, 0, 0))
  224. {
  225. if (_fTermThread && (GetTickCount() - _dwFirstShowTime) > MINSHOWTIME)
  226. {
  227. // we were signaled to finish and we have been visible MINSHOWTIME,
  228. // so its ok to quit
  229. break;
  230. }
  231. if (!IsDialogMessage(_hwndProgress, &msg))
  232. {
  233. TranslateMessage(&msg);
  234. DispatchMessage(&msg);
  235. }
  236. }
  237. }
  238. DestroyWindow(_hwndProgress);
  239. _hwndProgress = NULL;
  240. }
  241. // this is for callers that dont call stop
  242. ENTERCRITICAL;
  243. _fThreadRunning = FALSE;
  244. if (_fReleaseSelf)
  245. Release();
  246. LEAVECRITICAL;
  247. return 0;
  248. }
  249. DWORD FormatMessageWrapW(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageID, DWORD dwLangID, LPWSTR pwzBuffer, DWORD cchSize, ...)
  250. {
  251. va_list vaParamList;
  252. va_start(vaParamList, cchSize);
  253. DWORD dwResult = FormatMessageW(dwFlags, lpSource, dwMessageID, dwLangID, pwzBuffer, cchSize, &vaParamList);
  254. va_end(vaParamList);
  255. return dwResult;
  256. }
  257. DWORD FormatMessageWrapA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageID, DWORD dwLangID, LPSTR pszBuffer, DWORD cchSize, ...)
  258. {
  259. va_list vaParamList;
  260. va_start(vaParamList, cchSize);
  261. DWORD dwResult = FormatMessageA(dwFlags, lpSource, dwMessageID, dwLangID, pszBuffer, cchSize, &vaParamList);
  262. va_end(vaParamList);
  263. return dwResult;
  264. }
  265. #define TIME_DAYS_IN_YEAR 365
  266. #define TIME_HOURS_IN_DAY 24
  267. #define TIME_MINUTES_IN_HOUR 60
  268. #define TIME_SECONDS_IN_MINUTE 60
  269. void _FormatMessageWrapper(LPCTSTR pszTemplate, DWORD dwNum1, DWORD dwNum2, LPTSTR pszOut, DWORD cchSize)
  270. {
  271. // Is FormatMessageWrapW implemented?
  272. if (g_bRunOnNT5)
  273. {
  274. FormatMessageWrapW(FORMAT_MESSAGE_FROM_STRING, pszTemplate, 0, 0, pszOut, cchSize, dwNum1, dwNum2);
  275. }
  276. else
  277. {
  278. CHAR szOutAnsi[MAX_PATH];
  279. CHAR szTemplateAnsi[MAX_PATH];
  280. SHTCharToAnsi(pszTemplate, szTemplateAnsi, ARRAYSIZE(szTemplateAnsi));
  281. FormatMessageWrapA(FORMAT_MESSAGE_FROM_STRING, szTemplateAnsi, 0, 0, szOutAnsi, ARRAYSIZE(szOutAnsi), dwNum1, dwNum2);
  282. SHAnsiToTChar(szOutAnsi, pszOut, cchSize);
  283. }
  284. }
  285. #define CCH_TIMET_TEMPLATE_SIZE 120 // Should be good enough, even with localization bloat.
  286. #define CCH_TIME_SIZE 170 // Should be good enough, even with localization bloat.
  287. void _SetProgressLargeTimeEst(DWORD dwSecondsLeft, LPTSTR pszOut, DWORD cchSize)
  288. {
  289. // Yes.
  290. TCHAR szTemplate[CCH_TIMET_TEMPLATE_SIZE];
  291. DWORD dwMinutes = (dwSecondsLeft / TIME_SECONDS_IN_MINUTE);
  292. DWORD dwHours = (dwMinutes / TIME_MINUTES_IN_HOUR);
  293. DWORD dwDays = (dwHours / TIME_HOURS_IN_DAY);
  294. if (dwDays)
  295. {
  296. dwHours %= TIME_HOURS_IN_DAY;
  297. // It's more than a day, so display days and hours.
  298. if (1 == dwDays)
  299. {
  300. if (1 == dwHours)
  301. LoadString(MLGetHinst(), IDS_TIMEEST_DAYHOUR, szTemplate, ARRAYSIZE(szTemplate));
  302. else
  303. LoadString(MLGetHinst(), IDS_TIMEEST_DAYHOURS, szTemplate, ARRAYSIZE(szTemplate));
  304. }
  305. else
  306. {
  307. if (1 == dwHours)
  308. LoadString(MLGetHinst(), IDS_TIMEEST_DAYSHOUR, szTemplate, ARRAYSIZE(szTemplate));
  309. else
  310. LoadString(MLGetHinst(), IDS_TIMEEST_DAYSHOURS, szTemplate, ARRAYSIZE(szTemplate));
  311. }
  312. _FormatMessageWrapper(szTemplate, dwDays, dwHours, pszOut, cchSize);
  313. }
  314. else
  315. {
  316. // It's let than a day, so display hours and minutes.
  317. dwMinutes %= TIME_MINUTES_IN_HOUR;
  318. // It's more than a day, so display days and hours.
  319. if (1 == dwHours)
  320. {
  321. if (1 == dwMinutes)
  322. LoadString(MLGetHinst(), IDS_TIMEEST_HOURMIN, szTemplate, ARRAYSIZE(szTemplate));
  323. else
  324. LoadString(MLGetHinst(), IDS_TIMEEST_HOURMINS, szTemplate, ARRAYSIZE(szTemplate));
  325. }
  326. else
  327. {
  328. if (1 == dwMinutes)
  329. LoadString(MLGetHinst(), IDS_TIMEEST_HOURSMIN, szTemplate, ARRAYSIZE(szTemplate));
  330. else
  331. LoadString(MLGetHinst(), IDS_TIMEEST_HOURSMINS, szTemplate, ARRAYSIZE(szTemplate));
  332. }
  333. _FormatMessageWrapper(szTemplate, dwHours, dwMinutes, pszOut, cchSize);
  334. }
  335. }
  336. // This sets the "Seconds Left" text in the progress dialog
  337. void CProgressDialog::_SetProgressTimeEst(DWORD dwSecondsLeft)
  338. {
  339. TCHAR szFmt[CCH_TIMET_TEMPLATE_SIZE];
  340. TCHAR szOut[CCH_TIME_SIZE];
  341. DWORD dwTime;
  342. DWORD dwTickCount = GetTickCount();
  343. // Since the progress time has either a 1 minute or 5 second granularity (depending on whether the total time
  344. // remaining is greater or less than 1 minute), we only update it every 20 seconds if the total time is > 1 minute,
  345. // and ever 4 seconds if the time is < 1 minute. This keeps the time from flashing back and forth between
  346. // boundaries (eg 45 secondsand 40 seconds remaining).
  347. if (dwTickCount - _dwLastUpdatedTimeRemaining < (DWORD)((dwSecondsLeft > 60) ? 20000 : 4000))
  348. return;
  349. if (_fNoTime)
  350. {
  351. szOut[0] = TEXT('\0');
  352. }
  353. else
  354. {
  355. // Is it more than an hour?
  356. if (dwSecondsLeft > (TIME_SECONDS_IN_MINUTE * TIME_MINUTES_IN_HOUR))
  357. _SetProgressLargeTimeEst(dwSecondsLeft, szOut, ARRAYSIZE(szOut));
  358. else
  359. {
  360. // No.
  361. if (dwSecondsLeft > TIME_SECONDS_IN_MINUTE)
  362. {
  363. // Note that dwTime is at least 2, so we only need a plural form
  364. LoadString(MLGetHinst(), IDS_TIMEEST_MINUTES, szFmt, ARRAYSIZE(szFmt));
  365. dwTime = (dwSecondsLeft / TIME_SECONDS_IN_MINUTE) + 1;
  366. }
  367. else
  368. {
  369. LoadString(MLGetHinst(), IDS_TIMEEST_SECONDS, szFmt, ARRAYSIZE(szFmt));
  370. // Round up to 5 seconds so it doesn't look so random
  371. dwTime = ((dwSecondsLeft + 4) / 5) * 5;
  372. }
  373. wnsprintf(szOut, ARRAYSIZE(szOut), szFmt, dwTime);
  374. }
  375. }
  376. // we are updating now, so set the _dwLastUpdatedTimeRemaining to now
  377. _dwLastUpdatedTimeRemaining = dwTickCount;
  378. // update the Time remaining field
  379. SetDlgItemText(_hwndProgress, IDD_PROGDLG_LINE3, szOut);
  380. }
  381. #define MAX(x, y) ((x) > (y) ? (x) : (y))
  382. //
  383. // This function updates the ProgressTime field (aka Line3)
  384. //
  385. HRESULT CProgressDialog::_SetProgressTime(void)
  386. {
  387. DWORD dwSecondsLeft;
  388. DWORD dwTotal;
  389. DWORD dwCompleted;
  390. DWORD dwCurrentRate;
  391. DWORD dwTickDelta;
  392. DWORD dwLeft;
  393. DWORD dwCurrentTickCount;
  394. _iNumTimesSetProgressCalled++;
  395. // grab these in the crit sec (because they can change, and we need a matched set)
  396. ENTERCRITICAL;
  397. dwTotal = _dwTotal;
  398. dwCompleted = _dwCompleted;
  399. dwCurrentTickCount = _dwLastUpdatedTickCount;
  400. LEAVECRITICAL;
  401. dwLeft = dwTotal - dwCompleted;
  402. dwTickDelta = dwCurrentTickCount - _dwPrevTickCount;
  403. if (!dwTotal || !dwCompleted)
  404. return dwTotal ? S_FALSE : E_FAIL;
  405. // we divide the TickDelta by 100 to give tenths of seconds, so if we have recieved an
  406. // update faster than that, just skip it
  407. if (dwTickDelta < 100)
  408. {
  409. return S_FALSE;
  410. }
  411. TraceMsg(TF_PROGRESS, "Current tick count = %lu", dwCurrentTickCount);
  412. TraceMsg(TF_PROGRESS, "Total work = %lu", dwTotal);
  413. TraceMsg(TF_PROGRESS, "Completed work = %lu", dwCompleted);
  414. TraceMsg(TF_PROGRESS, "Prev. comp work= %lu", _dwPrevCompleted);
  415. TraceMsg(TF_PROGRESS, "Work left = %lu", dwLeft);
  416. TraceMsg(TF_PROGRESS, "Tick delta = %lu", dwTickDelta);
  417. if (dwTotal < dwCompleted)
  418. {
  419. // we can get into this case if we are applying attributes to sparse files
  420. // on a volume. As we add up the file sizes, we end up with a number that is bigger
  421. // than the drive size. We get rid of the time so that we wont show the user something
  422. // completely bogus
  423. _fNoTime = TRUE;
  424. dwTotal = dwCompleted + (dwCompleted >> 3); // fudge dwTotal forward a bit
  425. TraceMsg(TF_PROGRESS, "!! (Total < Completed), fudging Total work to = %lu", dwTotal);
  426. }
  427. if(dwCompleted <= _dwPrevCompleted)
  428. {
  429. // woah, we are going backwards, we dont deal w/ negative or zero rates so...
  430. dwCurrentRate = (_dwPrevRate ? _dwPrevRate : 2);
  431. }
  432. else
  433. {
  434. // calculate the current rate in points per tenth of a second
  435. dwTickDelta /= 100;
  436. if (0 == dwTickDelta)
  437. dwTickDelta = 1; // Protect from divide by zero
  438. dwCurrentRate = (dwCompleted - _dwPrevCompleted) / dwTickDelta;
  439. }
  440. TraceMsg(TF_PROGRESS, "Current rate = %lu", dwCurrentRate);
  441. TraceMsg(TF_PROGRESS, "Prev. rate = %lu", _dwPrevRate);
  442. // time remaining in seconds (we take a REAL average to smooth out random fluxuations)
  443. DWORD dwAverageRate = (DWORD)((dwCurrentRate + (_int64)_dwPrevRate * _iNumTimesSetProgressCalled) / (_iNumTimesSetProgressCalled + 1));
  444. TraceMsg(TF_PROGRESS, "Average rate= %lu", dwAverageRate);
  445. dwAverageRate = MAX(dwAverageRate, 1); // Protect from divide by zero
  446. dwSecondsLeft = (dwLeft / dwAverageRate) / 10;
  447. TraceMsg(TF_PROGRESS, "Seconds left = %lu", dwSecondsLeft);
  448. TraceMsg(TF_PROGRESS, "");
  449. // It would be odd to show "1 second left" and then immediately clear it, and to avoid showing
  450. // rediculous early estimates, we dont show anything until we have at least 5 data points
  451. if ((dwSecondsLeft >= MIN_MINTIME4FEEDBACK) && (_iNumTimesSetProgressCalled >= 5))
  452. {
  453. // display new estimate of time left
  454. _SetProgressTimeEst(dwSecondsLeft);
  455. }
  456. // set all the _dwPrev stuff for next time
  457. _dwPrevRate = dwAverageRate;
  458. _dwPrevTickCount = dwCurrentTickCount;
  459. _dwPrevCompleted = dwCompleted;
  460. return S_OK;
  461. }
  462. INT_PTR CALLBACK CProgressDialog::ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  463. {
  464. CProgressDialog * ppd = (CProgressDialog *)GetWindowLongPtr(hDlg, DWLP_USER);
  465. if (WM_INITDIALOG == wMsg)
  466. {
  467. SetWindowLongPtr(hDlg, DWLP_USER, lParam);
  468. ppd = (CProgressDialog *)lParam;
  469. }
  470. if (ppd)
  471. return ppd->_ProgressDialogProc(hDlg, wMsg, wParam, lParam);
  472. return DefWindowProc(hDlg, wMsg, wParam, lParam);
  473. }
  474. // apithk.c entry
  475. STDAPI_(void) ProgressSetMarqueeMode(HWND hwndProgress, BOOL bOn);
  476. void CProgressDialog::_ShowProgressBar(HWND hwnd)
  477. {
  478. if (hwnd)
  479. {
  480. HWND hwndPrgress = GetDlgItem(hwnd, IDD_PROGDLG_PROGRESSBAR);
  481. ProgressSetMarqueeMode(hwndPrgress, (SPBEGINF_MARQUEEPROGRESS & _spbeginf));
  482. UINT swp = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
  483. if (SPBEGINF_NOPROGRESSBAR & _spbeginf)
  484. swp |= SWP_HIDEWINDOW;
  485. else
  486. swp |= SWP_SHOWWINDOW;
  487. SetWindowPos(hwndPrgress, NULL, 0, 0, 0, 0, swp);
  488. }
  489. }
  490. BOOL CProgressDialog::_OnInit(HWND hDlg)
  491. {
  492. // dont minimize if the caller requests or is modal
  493. if ((SPINITF_MODAL | SPINITF_NOMINIMIZE) & _spinitf)
  494. {
  495. // The caller wants us to remove the Minimize Box or button in the caption bar.
  496. SHSetWindowBits(hDlg, GWL_STYLE, WS_MINIMIZEBOX, 0);
  497. }
  498. _ShowProgressBar(hDlg);
  499. return FALSE;
  500. }
  501. BOOL CProgressDialog::_ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  502. {
  503. BOOL fHandled = TRUE; // handled
  504. switch (wMsg)
  505. {
  506. case WM_INITDIALOG:
  507. return _OnInit(hDlg);
  508. case WM_SHOWWINDOW:
  509. if (wParam)
  510. {
  511. _SetModeless(FALSE);
  512. ASSERT(_hwndProgress);
  513. SetAnimation(_hInstAnimation, _idAnimation);
  514. // set the initial text values
  515. if (_pwzTitle)
  516. SetTitle(_pwzTitle);
  517. if (_pwzLine1)
  518. SetLine(1, _pwzLine1, FALSE, NULL);
  519. if (_pwzLine2)
  520. SetLine(2, _pwzLine2, FALSE, NULL);
  521. if (_pwzLine3)
  522. SetLine(3, _pwzLine3, FALSE, NULL);
  523. }
  524. break;
  525. case WM_DESTROY:
  526. _SetModeless(TRUE);
  527. if (_hwndDlgParent)
  528. {
  529. if (SHIsChildOrSelf(_hwndProgress, GetFocus()))
  530. SetForegroundWindow(_hwndDlgParent);
  531. }
  532. break;
  533. case WM_ENABLE:
  534. if (wParam)
  535. {
  536. // we assume that we were previously disabled and thus restart our tick counter
  537. // because we also naively assume that no work was being done while we were disabled
  538. _dwPrevTickCount = GetTickCount();
  539. }
  540. _PauseAnimation(wParam == 0);
  541. break;
  542. case WM_TIMER:
  543. if (wParam == ID_SHOWTIMER)
  544. {
  545. KillTimer(hDlg, ID_SHOWTIMER);
  546. _DisplayDialog();
  547. _dwFirstShowTime = GetTickCount();
  548. }
  549. break;
  550. case WM_COMMAND:
  551. if (IDCANCEL == GET_WM_COMMAND_ID(wParam, lParam))
  552. _UserCancelled();
  553. break;
  554. case PDM_SHUTDOWN:
  555. // Make sure this window is shown before telling the user there
  556. // is a problem. Ignore FOF_NOERRORUI here because of the
  557. // nature of the situation
  558. MLShellMessageBox(hDlg, MAKEINTRESOURCE(IDS_CANTSHUTDOWN), NULL, (MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND));
  559. break;
  560. case PDM_TERMTHREAD:
  561. // a dummy id that we can take so that folks can post to us and make
  562. // us go through the main loop
  563. break;
  564. case WM_SYSCOMMAND:
  565. switch(wParam)
  566. {
  567. case SC_MINIMIZE:
  568. _fMinimized = TRUE;
  569. break;
  570. case SC_RESTORE:
  571. SetTitle(_pwzTitle); // Restore title to original text.
  572. _fMinimized = FALSE;
  573. break;
  574. }
  575. fHandled = FALSE;
  576. break;
  577. case PDM_UPDATE:
  578. if (!_fCancel && IsWindowEnabled(hDlg))
  579. {
  580. _SetProgressTime();
  581. _UpdateProgressDialog();
  582. }
  583. // we are done processing the update
  584. _fChangePosted = FALSE;
  585. break;
  586. case WM_QUERYENDSESSION:
  587. // Post a message telling the dialog to show the "We can't shutdown now"
  588. // dialog and return to USER right away, so we don't have to worry about
  589. // the user not clicking the OK button before USER puts up its "this
  590. // app didn't respond" dialog
  591. PostMessage(hDlg, PDM_SHUTDOWN, 0, 0);
  592. // Make sure the dialog box procedure returns FALSE
  593. SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
  594. return(TRUE);
  595. default:
  596. fHandled = FALSE; // Not handled
  597. }
  598. return fHandled;
  599. }
  600. // This is used to asyncronously update the progess dialog.
  601. void CProgressDialog::_AsyncUpdate(void)
  602. {
  603. if (!_fChangePosted && _hwndProgress) // Prevent from posting too many messages.
  604. {
  605. // set the flag first because with async threads
  606. // the progress window could handle it and clear the
  607. // bit before we set it.. then we'd lose further messages
  608. // thinking that one was still pending
  609. _fChangePosted = TRUE;
  610. if (!PostMessage(_hwndProgress, PDM_UPDATE, 0, 0))
  611. {
  612. _fChangePosted = FALSE;
  613. }
  614. }
  615. }
  616. void CProgressDialog::_UpdateProgressDialog(void)
  617. {
  618. if (_fTotalChanged)
  619. {
  620. _fTotalChanged = FALSE;
  621. if (0x80000000 & _dwTotal)
  622. _fScaleBug = TRUE;
  623. SendMessage(GetDlgItem(_hwndProgress, IDD_PROGDLG_PROGRESSBAR), PBM_SETRANGE32, 0, (_fScaleBug ? (_dwTotal >> 1) : _dwTotal));
  624. }
  625. if (_fCompletedChanged)
  626. {
  627. _fCompletedChanged = FALSE;
  628. SendMessage(GetDlgItem(_hwndProgress, IDD_PROGDLG_PROGRESSBAR), PBM_SETPOS, (WPARAM) (_fScaleBug ? (_dwCompleted >> 1) : _dwCompleted), 0);
  629. }
  630. }
  631. void CProgressDialog::_PauseAnimation(BOOL bStop)
  632. {
  633. // only called from within the hwndProgress wndproc so assum it's there
  634. if (_hwndProgress)
  635. {
  636. if (bStop)
  637. {
  638. Animate_Stop(GetDlgItem(_hwndProgress, IDD_PROGDLG_ANIMATION));
  639. }
  640. else
  641. {
  642. Animate_Play(GetDlgItem(_hwndProgress, IDD_PROGDLG_ANIMATION), -1, -1, -1);
  643. }
  644. }
  645. }
  646. void CProgressDialog::_UserCancelled(void)
  647. {
  648. // Don't hide the dialog because the caller may not pole
  649. // ::HasUserCancelled() for quite a while.
  650. // ShowWindow(hDlg, SW_HIDE);
  651. _fCancel = TRUE;
  652. // give minimal feedback that the cancel click was accepted
  653. EnableWindow(GetDlgItem(_hwndProgress, IDCANCEL), FALSE);
  654. // If the user cancels, Line 1 & 2 will be cleared and Line 3 will get this msg.
  655. if (!_pwzCancelMsg)
  656. {
  657. WCHAR wzDefaultMsg[MAX_PATH];
  658. LoadStringW(MLGetHinst(), IDS_DEFAULT_CANCELPROG, wzDefaultMsg, ARRAYSIZE(wzDefaultMsg));
  659. Str_SetPtr(&_pwzCancelMsg, wzDefaultMsg);
  660. }
  661. SetLine(1, L"", FALSE, NULL);
  662. SetLine(2, L"", FALSE, NULL);
  663. SetLine(3, _pwzCancelMsg, FALSE, NULL);
  664. }
  665. HRESULT CProgressDialog::Initialize(SPINITF flags, LPCWSTR pszTitle, LPCWSTR pszCancel)
  666. {
  667. if (!_fInitialized)
  668. {
  669. _spinitf = flags;
  670. if (pszTitle)
  671. SetTitle(pszTitle);
  672. if (pszCancel)
  673. SetCancelMsg(pszCancel, NULL);
  674. _fInitialized = TRUE;
  675. return S_OK;
  676. }
  677. return E_UNEXPECTED;
  678. }
  679. void CProgressDialog::_SetModeless(BOOL fModeless)
  680. {
  681. // if the user is requesting a modal window, disable the parent now.
  682. if (_spinitf & SPINITF_MODAL)
  683. {
  684. if (FAILED(IUnknown_EnableModless(_punkSite, fModeless))
  685. && _hwndDlgParent)
  686. {
  687. EnableWindow(_hwndDlgParent, fModeless);
  688. }
  689. }
  690. }
  691. HRESULT CProgressDialog::_BeginAction(SPBEGINF flags)
  692. {
  693. _spbeginf = flags;
  694. _fTermThread = FALSE;
  695. _fTotalChanged = TRUE;
  696. if (!_fThreadRunning)
  697. {
  698. SHCreateThread(CProgressDialog::ThreadProc, this, CTF_FREELIBANDEXIT, CProgressDialog::SyncThreadProc);
  699. // _fThreadRunning is set in _SyncThreadProc()
  700. }
  701. if (_fThreadRunning)
  702. {
  703. _fInAction = TRUE;
  704. _ShowProgressBar(_hwndProgress);
  705. // initialize the _dwPrev counters
  706. _dwPrevRate = 0;
  707. _dwPrevCompleted = 0;
  708. _dwPrevTickCount = GetTickCount();
  709. TraceMsg(TF_PROGRESS, "Initial tick count = %lu", _dwPrevTickCount);
  710. return S_OK;
  711. }
  712. return E_OUTOFMEMORY;
  713. }
  714. #define ACTIONENTRY(a, dll, id) {a, dll, id}
  715. #define c_szShell32 "shell32.dll"
  716. #define c_szShdocvw "shdocvw.dll"
  717. const static struct
  718. {
  719. SPACTION action;
  720. LPCSTR pszDll;
  721. UINT id;
  722. }
  723. c_spActions[] =
  724. {
  725. ACTIONENTRY(SPACTION_MOVING, c_szShell32, 160), // IDA_FILEMOVE
  726. ACTIONENTRY(SPACTION_COPYING, c_szShell32, 161), // IDA_FILECOPY
  727. ACTIONENTRY(SPACTION_RECYCLING, c_szShell32, 162), // IDA_FILEDEL
  728. ACTIONENTRY(SPACTION_APPLYINGATTRIBS, c_szShell32, 165), // IDA_APPLYATTRIBS
  729. ACTIONENTRY(SPACTION_DOWNLOADING, c_szShdocvw, 0x100),
  730. ACTIONENTRY(SPACTION_SEARCHING_INTERNET, c_szShell32, 166), // IDA_ISEARCH
  731. ACTIONENTRY(SPACTION_SEARCHING_FILES, c_szShell32, 150) // IDA_SEARCH
  732. };
  733. HRESULT CProgressDialog::Begin(SPACTION action, SPBEGINF flags)
  734. {
  735. if (_fInAction || !_fInitialized)
  736. return E_FAIL;
  737. HRESULT hr = S_OK;
  738. for (int i = 0; i < ARRAYSIZE(c_spActions); i++)
  739. {
  740. if (c_spActions[i].action == action)
  741. {
  742. HINSTANCE hinst = LoadLibraryA(c_spActions[i].pszDll);
  743. if (hinst)
  744. {
  745. hr = SetAnimation(hinst, c_spActions[i].id);
  746. if (_hinstFree)
  747. FreeLibrary(_hinstFree);
  748. _hinstFree = hinst;
  749. }
  750. break;
  751. }
  752. }
  753. if (SUCCEEDED(hr))
  754. {
  755. if (!_hwndDlgParent)
  756. IUnknown_GetWindow(_punkSite, &_hwndDlgParent);
  757. hr = _BeginAction(flags);
  758. }
  759. return hr;
  760. }
  761. #define SPINIT_MASK (SPINITF_MODAL | SPINITF_NOMINIMIZE)
  762. #define SPBEGIN_MASK 0x1F
  763. // IProgressDialog
  764. HRESULT CProgressDialog::StartProgressDialog(HWND hwndParent, IUnknown * punkNotUsed, DWORD dwFlags, LPCVOID pvResevered)
  765. {
  766. if (_fInAction)
  767. return S_OK;
  768. HRESULT hr = Initialize(dwFlags & SPINIT_MASK, NULL, NULL);
  769. if (SUCCEEDED(hr))
  770. {
  771. _fNoTime = dwFlags & PROGDLG_NOTIME;
  772. // we dont Save punkNotUsed
  773. _hwndDlgParent = hwndParent;
  774. hr = _BeginAction(dwFlags & SPBEGIN_MASK);
  775. }
  776. return hr;
  777. }
  778. HRESULT CProgressDialog::End()
  779. {
  780. ASSERT(_fInitialized && _fInAction);
  781. // possibly need to pop stack or change state
  782. _fInAction = FALSE;
  783. _spbeginf = 0;
  784. return S_OK;
  785. }
  786. HRESULT CProgressDialog::Stop()
  787. {
  788. ASSERT(!_fInAction);
  789. BOOL fFocusParent = FALSE;
  790. // shut down the progress dialog
  791. if (_fThreadRunning)
  792. {
  793. ASSERT(_hwndProgress);
  794. _fTermThread = TRUE;
  795. PostMessage(_hwndProgress, PDM_TERMTHREAD, 0, 0);
  796. }
  797. return S_OK;
  798. }
  799. HRESULT CProgressDialog::StopProgressDialog(void)
  800. {
  801. // callers can call this over and over
  802. if (_fInAction)
  803. End();
  804. return Stop();
  805. }
  806. HRESULT CProgressDialog::SetTitle(LPCWSTR pwzTitle)
  807. {
  808. HRESULT hr = S_OK;
  809. // Does the dialog exist?
  810. if (_hwndProgress)
  811. {
  812. // Yes, so put the value directly into the dialog.
  813. if (!SetWindowTextW(_hwndProgress, (pwzTitle ? pwzTitle : L"")))
  814. hr = E_FAIL;
  815. }
  816. else
  817. Str_SetPtrW(&_pwzTitle, pwzTitle);
  818. return hr;
  819. }
  820. HRESULT CProgressDialog::SetAnimation(HINSTANCE hInstAnimation, UINT idAnimation)
  821. {
  822. HRESULT hr = S_OK;
  823. _hInstAnimation = hInstAnimation;
  824. _idAnimation = idAnimation;
  825. // Does the dialog exist?
  826. if (_hwndProgress)
  827. {
  828. if (!Animate_OpenEx(GetDlgItem(_hwndProgress, IDD_PROGDLG_ANIMATION), _hInstAnimation, IntToPtr(_idAnimation)))
  829. hr = E_FAIL;
  830. }
  831. return hr;
  832. }
  833. HRESULT CProgressDialog::UpdateText(SPTEXT sptext, LPCWSTR pszText, BOOL fMayCompact)
  834. {
  835. if (_fInitialized)
  836. return SetLine((DWORD)sptext, pszText, fMayCompact, NULL);
  837. else
  838. return E_UNEXPECTED;
  839. }
  840. HRESULT CProgressDialog::SetLine(DWORD dwLineNum, LPCWSTR pwzString, BOOL fCompactPath, LPCVOID pvResevered)
  841. {
  842. HRESULT hr = E_INVALIDARG;
  843. switch (dwLineNum)
  844. {
  845. case 1:
  846. hr = _SetLineHelper(pwzString, &_pwzLine1, IDD_PROGDLG_LINE1, fCompactPath);
  847. break;
  848. case 2:
  849. hr = _SetLineHelper(pwzString, &_pwzLine2, IDD_PROGDLG_LINE2, fCompactPath);
  850. break;
  851. case 3:
  852. if (_spbeginf & SPBEGINF_AUTOTIME)
  853. {
  854. // you cant change line3 directly if you want PROGDLG_AUTOTIME, because
  855. // this is updated by the progress dialog automatically
  856. // unless we're cancelling
  857. ASSERT(_fCancel);
  858. hr = _fCancel ? S_OK : E_INVALIDARG;
  859. break;
  860. }
  861. hr = _SetLineHelper(pwzString, &_pwzLine3, IDD_PROGDLG_LINE3, fCompactPath);
  862. break;
  863. default:
  864. ASSERT(0);
  865. }
  866. return hr;
  867. }
  868. HRESULT CProgressDialog::SetCancelMsg(LPCWSTR pwzCancelMsg, LPCVOID pvResevered)
  869. {
  870. Str_SetPtr(&_pwzCancelMsg, pwzCancelMsg); // If the user cancels, Line 1 & 2 will be cleared and Line 3 will get this msg.
  871. return S_OK;
  872. }
  873. HRESULT CProgressDialog::Timer(DWORD dwAction, LPCVOID pvResevered)
  874. {
  875. HRESULT hr = E_NOTIMPL;
  876. switch (dwAction)
  877. {
  878. case PDTIMER_RESET:
  879. _dwPrevTickCount = GetTickCount();
  880. hr = S_OK;
  881. break;
  882. }
  883. return hr;
  884. }
  885. HRESULT CProgressDialog::SetProgress(DWORD dwCompleted, DWORD dwTotal)
  886. {
  887. DWORD dwTickCount = GetTickCount(); // get the tick count before taking the critical section
  888. // we grab the crit section in case the UI thread is trying to access
  889. // _dwCompleted, _dwTotal or _dwLastUpdatedTickCount to do its time update.
  890. ENTERCRITICAL;
  891. if (_dwCompleted != dwCompleted)
  892. {
  893. _dwCompleted = dwCompleted;
  894. _fCompletedChanged = TRUE;
  895. }
  896. if (_dwTotal != dwTotal)
  897. {
  898. _dwTotal = dwTotal;
  899. _fTotalChanged = TRUE;
  900. }
  901. if (_fCompletedChanged || _fTotalChanged)
  902. {
  903. _dwLastUpdatedTickCount = dwTickCount;
  904. }
  905. LEAVECRITICAL;
  906. #ifdef DEBUG
  907. if (_dwCompleted > _dwTotal)
  908. {
  909. TraceMsg(TF_WARNING, "CProgressDialog::SetProgress(_dwCompleted > _dwTotal ?!?!)");
  910. }
  911. #endif
  912. if (_fCompletedChanged || _fTotalChanged)
  913. {
  914. // something changed, so update the progress dlg
  915. _AsyncUpdate();
  916. }
  917. TraceMsg(TF_PROGRESS, "CProgressDialog::SetProgress(Complete=%#08lx, Total=%#08lx)", dwCompleted, dwTotal);
  918. if (_fMinimized)
  919. {
  920. _SetTitleBarProgress(dwCompleted, dwTotal);
  921. }
  922. return S_OK;
  923. }
  924. HRESULT CProgressDialog::UpdateProgress(ULONGLONG ulCompleted, ULONGLONG ulTotal)
  925. {
  926. if (_fInitialized && _fInAction)
  927. return SetProgress64(ulCompleted, ulTotal);
  928. else
  929. return E_UNEXPECTED;
  930. }
  931. HRESULT CProgressDialog::SetProgress64(ULONGLONG ullCompleted, ULONGLONG ullTotal)
  932. {
  933. ULARGE_INTEGER uliCompleted, uliTotal;
  934. uliCompleted.QuadPart = ullCompleted;
  935. uliTotal.QuadPart = ullTotal;
  936. // If we are using the top 32 bits, scale both numbers down.
  937. // Note that I'm using the attribute that dwTotalHi is always
  938. // larger than dwCompletedHi
  939. ASSERT(uliTotal.HighPart >= uliCompleted.HighPart);
  940. while (uliTotal.HighPart)
  941. {
  942. uliCompleted.QuadPart >>= 1;
  943. uliTotal.QuadPart >>= 1;
  944. }
  945. ASSERT((0 == uliCompleted.HighPart) && (0 == uliTotal.HighPart)); // Make sure we finished scaling down.
  946. return SetProgress(uliCompleted.LowPart, uliTotal.LowPart);
  947. }
  948. HRESULT CProgressDialog::_SetTitleBarProgress(DWORD dwCompleted, DWORD dwTotal)
  949. {
  950. TCHAR szTemplate[MAX_PATH];
  951. TCHAR szTitle[MAX_PATH];
  952. int nPercent = 0;
  953. if (dwTotal) // Disallow divide by zero.
  954. {
  955. // Will scaling it up cause a wrap?
  956. if ((100 * 100) <= dwTotal)
  957. {
  958. // Yes, so scale down.
  959. nPercent = (dwCompleted / (dwTotal / 100));
  960. }
  961. else
  962. {
  963. // No, so scale up.
  964. nPercent = ((100 * dwCompleted) / dwTotal);
  965. }
  966. }
  967. LoadString(MLGetHinst(), IDS_TITLEBAR_PROGRESS, szTemplate, ARRAYSIZE(szTemplate));
  968. wnsprintf(szTitle, ARRAYSIZE(szTitle), szTemplate, nPercent);
  969. SetWindowText(_hwndProgress, szTitle);
  970. return S_OK;
  971. }
  972. HRESULT CProgressDialog::ResetCancel()
  973. {
  974. _fCancel = FALSE;
  975. if (_pwzLine1)
  976. SetLine(1, _pwzLine1, FALSE, NULL);
  977. if (_pwzLine2)
  978. SetLine(2, _pwzLine2, FALSE, NULL);
  979. if (_pwzLine3)
  980. SetLine(3, _pwzLine3, FALSE, NULL);
  981. return S_OK;
  982. }
  983. HRESULT CProgressDialog::QueryCancel(BOOL * pfCancelled)
  984. {
  985. *pfCancelled = HasUserCancelled();
  986. return S_OK;
  987. }
  988. /****************************************************\
  989. DESCRIPTION:
  990. This queries the progress dialog for a cancel and yields.
  991. it also will show the progress dialog if a certain amount of time has passed
  992. returns:
  993. TRUE cacnel was pressed, abort the operation
  994. FALSE continue
  995. \****************************************************/
  996. BOOL CProgressDialog::HasUserCancelled(void)
  997. {
  998. if (!_fCancel && _hwndProgress)
  999. {
  1000. MSG msg;
  1001. // win95 handled messages in here.
  1002. // we need to do the same in order to flush the input queue as well as
  1003. // for backwards compatability.
  1004. // we need to flush the input queue now because hwndProgress is
  1005. // on a different thread... which means it has attached thread inputs
  1006. // inorder to unlock the attached threads, we need to remove some
  1007. // sort of message until there's none left... any type of message..
  1008. while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  1009. {
  1010. if (!IsDialogMessage(_hwndProgress, &msg))
  1011. {
  1012. TranslateMessage(&msg);
  1013. DispatchMessage(&msg);
  1014. }
  1015. }
  1016. if (_fTotalChanged || _fCompletedChanged)
  1017. _AsyncUpdate();
  1018. }
  1019. return _fCancel;
  1020. }
  1021. // IOleWindow
  1022. HRESULT CProgressDialog::GetWindow(HWND * phwnd)
  1023. {
  1024. HRESULT hr = E_FAIL;
  1025. *phwnd = _hwndProgress;
  1026. if (_hwndProgress)
  1027. hr = S_OK;
  1028. return hr;
  1029. }
  1030. HRESULT CProgressDialog::QueryInterface(REFIID riid, void **ppvObj)
  1031. {
  1032. static const QITAB qit[] = {
  1033. QITABENT(CProgressDialog, IProgressDialog),
  1034. QITABENT(CProgressDialog, IActionProgressDialog),
  1035. QITABENT(CProgressDialog, IActionProgress),
  1036. QITABENT(CProgressDialog, IObjectWithSite),
  1037. QITABENT(CProgressDialog, IOleWindow),
  1038. { 0 },
  1039. };
  1040. return QISearch(this, qit, riid, ppvObj);
  1041. }
  1042. ULONG CProgressDialog::AddRef(void)
  1043. {
  1044. return InterlockedIncrement(&_cRef);
  1045. }
  1046. ULONG CProgressDialog::Release(void)
  1047. {
  1048. if (InterlockedDecrement(&_cRef))
  1049. return _cRef;
  1050. if (_fThreadRunning)
  1051. {
  1052. // need to keep this thread's ref around
  1053. // for a while longer to avoid the race
  1054. // to destroy this object on the dialog thread
  1055. AddRef();
  1056. ENTERCRITICAL;
  1057. if (_fThreadRunning)
  1058. {
  1059. // we call addref
  1060. AddRef();
  1061. _fReleaseSelf = TRUE;
  1062. }
  1063. LEAVECRITICAL;
  1064. Stop();
  1065. Release();
  1066. }
  1067. else
  1068. delete this;
  1069. return 0;
  1070. }
  1071. CProgressDialog::CProgressDialog() : _cRef(1)
  1072. {
  1073. DllAddRef();
  1074. // ASSERT zero initialized because we can only be created in the heap. (Private destructor)
  1075. ASSERT(!_pwzLine1);
  1076. ASSERT(!_pwzLine2);
  1077. ASSERT(!_pwzLine3);
  1078. ASSERT(!_fCancel);
  1079. ASSERT(!_fTermThread);
  1080. ASSERT(!_fInAction);
  1081. ASSERT(!_hwndProgress);
  1082. ASSERT(!_hwndDlgParent);
  1083. ASSERT(!_fChangePosted);
  1084. ASSERT(!_dwLastUpdatedTimeRemaining);
  1085. ASSERT(!_dwCompleted);
  1086. ASSERT(!_fCompletedChanged);
  1087. ASSERT(!_fTotalChanged);
  1088. ASSERT(!_fMinimized);
  1089. _dwTotal = 1; // Init to Completed=0, Total=1 so we are at 0%.
  1090. }
  1091. CProgressDialog::~CProgressDialog()
  1092. {
  1093. ASSERT(!_fInAction);
  1094. ASSERT(!_fThreadRunning);
  1095. Str_SetPtrW(&_pwzTitle, NULL);
  1096. Str_SetPtrW(&_pwzLine1, NULL);
  1097. Str_SetPtrW(&_pwzLine2, NULL);
  1098. Str_SetPtrW(&_pwzLine3, NULL);
  1099. if (_hinstFree)
  1100. FreeLibrary(_hinstFree);
  1101. DllRelease();
  1102. }
  1103. STDAPI CProgressDialog_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  1104. {
  1105. // aggregation checking is handled in class factory
  1106. *ppunk = NULL;
  1107. CProgressDialog * pProgDialog = new CProgressDialog();
  1108. if (pProgDialog)
  1109. {
  1110. *ppunk = SAFECAST(pProgDialog, IProgressDialog *);
  1111. return S_OK;
  1112. }
  1113. return E_OUTOFMEMORY;
  1114. }