windows-nt/Source/XPSP1/NT/shell/browseui/progress.cpp
2020-09-26 16:20:57 +08:00

1353 lines
41 KiB
C++

// Display the Progress Dialog for the progress on the completion of some
// generic operation. This is most often used for Deleting, Uploading, Copying,
// Moving and Downloading large numbers of files.
#include "priv.h"
#include "resource.h"
#include "mluisupp.h"
// this is how long we wait for the UI thread to create the progress hwnd before giving up
#define WAIT_PROGRESS_HWND 10*1000 // ten seconds
STDAPI CProgressDialog_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi);
class CProgressDialog
: public IProgressDialog
, public IOleWindow
, public IActionProgressDialog
, public IActionProgress
, public IObjectWithSite
{
public:
CProgressDialog();
// IUnknown
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
// IProgressDialog
STDMETHODIMP StartProgressDialog(HWND hwndParent, IUnknown * punkEnableModless, DWORD dwFlags, LPCVOID pvResevered);
STDMETHODIMP StopProgressDialog(void);
STDMETHODIMP SetTitle(LPCWSTR pwzTitle);
STDMETHODIMP SetAnimation(HINSTANCE hInstAnimation, UINT idAnimation);
STDMETHODIMP_(BOOL) HasUserCancelled(void);
STDMETHODIMP SetProgress(DWORD dwCompleted, DWORD dwTotal);
STDMETHODIMP SetProgress64(ULONGLONG ullCompleted, ULONGLONG ullTotal);
STDMETHODIMP SetLine(DWORD dwLineNum, LPCWSTR pwzString, BOOL fCompactPath, LPCVOID pvResevered);
STDMETHODIMP SetCancelMsg(LPCWSTR pwzCancelMsg, LPCVOID pvResevered);
STDMETHODIMP Timer(DWORD dwAction, LPCVOID pvResevered);
// IOleWindow
STDMETHODIMP GetWindow(HWND * phwnd);
STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; }
// IActionProgressDialog
STDMETHODIMP Initialize(SPINITF flags, LPCWSTR pszTitle, LPCWSTR pszCancel);
STDMETHODIMP Stop();
// IActionProgress
STDMETHODIMP Begin(SPACTION action, SPBEGINF flags);
STDMETHODIMP UpdateProgress(ULONGLONG ulCompleted, ULONGLONG ulTotal);
STDMETHODIMP UpdateText(SPTEXT sptext, LPCWSTR pszText, BOOL fMayCompact);
STDMETHODIMP QueryCancel(BOOL * pfCancelled);
STDMETHODIMP ResetCancel();
STDMETHODIMP End();
// IObjectWithSite
STDMETHODIMP SetSite(IUnknown *punk) { IUnknown_Set(&_punkSite, punk); return S_OK; }
STDMETHODIMP GetSite(REFIID riid, void **ppv) { *ppv = 0; return _punkSite ? _punkSite->QueryInterface(riid, ppv) : E_FAIL;}
// Other Public Methods
static INT_PTR CALLBACK ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
static DWORD CALLBACK ThreadProc(LPVOID pvThis) { return ((CProgressDialog *) pvThis)->_ThreadProc(); };
static DWORD CALLBACK SyncThreadProc(LPVOID pvThis) { return ((CProgressDialog *) pvThis)->_SyncThreadProc(); };
private:
~CProgressDialog(void);
LONG _cRef;
// State Accessible thru IProgressDialog
LPWSTR _pwzTitle; // This will be used to cache the value passed to IProgressDialog::SetTitle() until the dialog is displayed
UINT _idAnimation;
HINSTANCE _hInstAnimation;
LPWSTR _pwzLine1; // NOTE:
LPWSTR _pwzLine2; // these are only used to init the dialog, otherwise, we just
LPWSTR _pwzLine3; // call through on the main thread to update the dialog directly.
LPWSTR _pwzCancelMsg; // If the user cancels, Line 1 & 2 will be cleared and Line 3 will get this msg.
// Other internal state.
HWND _hwndDlgParent; // parent window for message boxes
HWND _hwndProgress; // dialog/progress window
DWORD _dwFirstShowTime; // tick count when the dialog was first shown (needed so we don't flash it up for an instant)
SPINITF _spinitf;
SPBEGINF _spbeginf;
IUnknown *_punkSite;
HINSTANCE _hinstFree;
BOOL _fCompletedChanged; // has the _dwCompleted changed since last time?
BOOL _fTotalChanged; // has the _dwTotal changed since last time?
BOOL _fChangePosted; // is there a change pending?
BOOL _fCancel;
BOOL _fTermThread;
BOOL _fThreadRunning;
BOOL _fInAction;
BOOL _fMinimized;
BOOL _fScaleBug; // Comctl32's PBM_SETRANGE32 msg will still cast it to an (int), so don't let the high bit be set.
BOOL _fNoTime;
BOOL _fReleaseSelf;
BOOL _fInitialized;
// Progress Values and Calculations
DWORD _dwCompleted; // progress completed
DWORD _dwTotal; // total progress
DWORD _dwPrevRate; // previous progress rate (used for computing time remaining)
DWORD _dwPrevTickCount; // the tick count when we last updated the progress time
DWORD _dwPrevCompleted; // the ammount we had completed when we last updated the progress time
DWORD _dwLastUpdatedTimeRemaining;// tick count when we last update the "Time remaining" field, we only update it every 5 seconds
DWORD _dwLastUpdatedTickCount; // tick count when SetProgress was last called, used to calculate the rate
UINT _iNumTimesSetProgressCalled;// how many times has the user called SetProgress?
// Private Member Functions
DWORD _ThreadProc(void);
DWORD _SyncThreadProc(void);
BOOL _OnInit(HWND hDlg);
BOOL _ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
void _PauseAnimation(BOOL bStop);
void _UpdateProgressDialog(void);
void _AsyncUpdate(void);
HRESULT _SetProgressTime(void);
void _SetProgressTimeEst(DWORD dwSecondsLeft);
void _UserCancelled(void);
HRESULT _DisplayDialog(void);
void _CompactProgressPath(LPCWSTR pwzStrIn, BOOL fCompactPath, UINT idDlgItem, LPWSTR pwzStrOut, DWORD cchSize);
HRESULT _SetLineHelper(LPCWSTR pwzNew, LPWSTR * ppwzDest, UINT idDlgItem, BOOL fCompactPath);
HRESULT _SetTitleBarProgress(DWORD dwCompleted, DWORD dwTotal);
HRESULT _BeginAction(SPBEGINF flags);
void _SetModeless(BOOL fModeless);
void _ShowProgressBar(HWND hwnd);
};
//#define TF_PROGRESS 0xFFFFFFFF
#define TF_PROGRESS 0x00000000
// REVIEW, we should tune this size down as small as we can
// to get smoother multitasking (without effecting performance)
#define MIN_MINTIME4FEEDBACK 5 // is it worth showing estimated time to completion feedback?
#define MS_TIMESLICE 2000 // ms, (MUST be > 1000!) first average time to completion estimate
#define SHOW_PROGRESS_TIMEOUT 1000 // 1 second
#define MINSHOWTIME 2000 // 2 seconds
// progress dialog message
#define PDM_SHUTDOWN WM_APP
#define PDM_TERMTHREAD (WM_APP + 1)
#define PDM_UPDATE (WM_APP + 2)
// progress dialog timer messages
#define ID_SHOWTIMER 1
#ifndef UNICODE
#error "This code will only compile UNICODE for perf reasons. If you really need an ANSI browseui, write all the code to convert."
#endif // !UNICODE
// compacts path strings to fit into the Text1 and Text2 fields
void CProgressDialog::_CompactProgressPath(LPCWSTR pwzStrIn, BOOL fCompactPath, UINT idDlgItem, LPWSTR pwzStrOut, DWORD cchSize)
{
WCHAR wzFinalPath[MAX_PATH];
LPWSTR pwzPathToUse = (LPWSTR)pwzStrIn;
// We don't compact the path if the dialog isn't displayed yet.
if (fCompactPath && _hwndProgress)
{
RECT rc;
int cxWidth;
StrCpyNW(wzFinalPath, (pwzStrIn ? pwzStrIn : L""), ARRAYSIZE(wzFinalPath));
// get the size of the text boxes
HWND hwnd = GetDlgItem(_hwndProgress, idDlgItem);
if (EVAL(hwnd))
{
HDC hdc;
HFONT hfont;
HFONT hfontSave;
hdc = GetDC(_hwndProgress);
hfont = (HFONT)SendMessage(_hwndProgress, WM_GETFONT, 0, 0);
hfontSave = (HFONT)SelectObject(hdc, hfont);
GetWindowRect(hwnd, &rc);
cxWidth = rc.right - rc.left;
ASSERT(cxWidth >= 0);
PathCompactPathW(hdc, wzFinalPath, cxWidth);
SelectObject(hdc, hfontSave);
ReleaseDC(_hwndProgress, hdc);
}
pwzPathToUse = wzFinalPath;
}
StrCpyNW(pwzStrOut, (pwzPathToUse ? pwzPathToUse : L""), cchSize);
}
HRESULT CProgressDialog::_SetLineHelper(LPCWSTR pwzNew, LPWSTR * ppwzDest, UINT idDlgItem, BOOL fCompactPath)
{
WCHAR wzFinalPath[MAX_PATH];
_CompactProgressPath(pwzNew, fCompactPath, idDlgItem, wzFinalPath, ARRAYSIZE(wzFinalPath));
Str_SetPtrW(ppwzDest, wzFinalPath); // No, so cache the value for later.
// Does the dialog exist?
if (_hwndProgress)
SetDlgItemText(_hwndProgress, idDlgItem, wzFinalPath);
return S_OK;
}
HRESULT CProgressDialog::_DisplayDialog(void)
{
TraceMsg(TF_PROGRESS, "CProgressDialog::_DisplayDialog()");
// Don't force ourselves into the foreground if a window we parented is already in the foreground:
// This is part of the fix for NT bug 298163 (the confirm replace dialog was deactivated
// by the progress dialog)
HWND hwndCurrent = GetForegroundWindow();
BOOL fChildIsForeground = FALSE;
while (NULL != (hwndCurrent = GetParent(hwndCurrent)))
{
if (_hwndProgress == hwndCurrent)
{
fChildIsForeground = TRUE;
break;
}
}
if (fChildIsForeground)
{
ShowWindow(_hwndProgress, SW_SHOWNOACTIVATE);
}
else
{
ShowWindow(_hwndProgress, SW_SHOW);
SetForegroundWindow(_hwndProgress);
}
SetFocus(GetDlgItem(_hwndProgress, IDCANCEL));
return S_OK;
}
DWORD CProgressDialog::_SyncThreadProc()
{
_InitComCtl32(); // Get ready for the Native Font Control
_hwndProgress = CreateDialogParam(MLGetHinst(), MAKEINTRESOURCE(DLG_PROGRESSDIALOG),
_hwndDlgParent, ProgressDialogProc, (LPARAM)this);
_fThreadRunning = (_hwndProgress != NULL);
return TRUE;
}
DWORD CProgressDialog::_ThreadProc(void)
{
if (_hwndProgress)
{
// WARNING - copy perf goes way down if this is normal or
// better priority. the default thread pri should be low.
// however if there are situations in which it should be higher,
// we can add SPBEGINF bits to support it.
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
SetTimer(_hwndProgress, ID_SHOWTIMER, SHOW_PROGRESS_TIMEOUT, NULL);
// Did if finish while we slept?
if (!_fTermThread)
{
// No, so display the dialog.
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
if (_fTermThread && (GetTickCount() - _dwFirstShowTime) > MINSHOWTIME)
{
// we were signaled to finish and we have been visible MINSHOWTIME,
// so its ok to quit
break;
}
if (!IsDialogMessage(_hwndProgress, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
DestroyWindow(_hwndProgress);
_hwndProgress = NULL;
}
// this is for callers that dont call stop
ENTERCRITICAL;
_fThreadRunning = FALSE;
if (_fReleaseSelf)
Release();
LEAVECRITICAL;
return 0;
}
DWORD FormatMessageWrapW(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageID, DWORD dwLangID, LPWSTR pwzBuffer, DWORD cchSize, ...)
{
va_list vaParamList;
va_start(vaParamList, cchSize);
DWORD dwResult = FormatMessageW(dwFlags, lpSource, dwMessageID, dwLangID, pwzBuffer, cchSize, &vaParamList);
va_end(vaParamList);
return dwResult;
}
DWORD FormatMessageWrapA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageID, DWORD dwLangID, LPSTR pszBuffer, DWORD cchSize, ...)
{
va_list vaParamList;
va_start(vaParamList, cchSize);
DWORD dwResult = FormatMessageA(dwFlags, lpSource, dwMessageID, dwLangID, pszBuffer, cchSize, &vaParamList);
va_end(vaParamList);
return dwResult;
}
#define TIME_DAYS_IN_YEAR 365
#define TIME_HOURS_IN_DAY 24
#define TIME_MINUTES_IN_HOUR 60
#define TIME_SECONDS_IN_MINUTE 60
void _FormatMessageWrapper(LPCTSTR pszTemplate, DWORD dwNum1, DWORD dwNum2, LPTSTR pszOut, DWORD cchSize)
{
// Is FormatMessageWrapW implemented?
if (g_bRunOnNT5)
{
FormatMessageWrapW(FORMAT_MESSAGE_FROM_STRING, pszTemplate, 0, 0, pszOut, cchSize, dwNum1, dwNum2);
}
else
{
CHAR szOutAnsi[MAX_PATH];
CHAR szTemplateAnsi[MAX_PATH];
SHTCharToAnsi(pszTemplate, szTemplateAnsi, ARRAYSIZE(szTemplateAnsi));
FormatMessageWrapA(FORMAT_MESSAGE_FROM_STRING, szTemplateAnsi, 0, 0, szOutAnsi, ARRAYSIZE(szOutAnsi), dwNum1, dwNum2);
SHAnsiToTChar(szOutAnsi, pszOut, cchSize);
}
}
#define CCH_TIMET_TEMPLATE_SIZE 120 // Should be good enough, even with localization bloat.
#define CCH_TIME_SIZE 170 // Should be good enough, even with localization bloat.
void _SetProgressLargeTimeEst(DWORD dwSecondsLeft, LPTSTR pszOut, DWORD cchSize)
{
// Yes.
TCHAR szTemplate[CCH_TIMET_TEMPLATE_SIZE];
DWORD dwMinutes = (dwSecondsLeft / TIME_SECONDS_IN_MINUTE);
DWORD dwHours = (dwMinutes / TIME_MINUTES_IN_HOUR);
DWORD dwDays = (dwHours / TIME_HOURS_IN_DAY);
if (dwDays)
{
dwHours %= TIME_HOURS_IN_DAY;
// It's more than a day, so display days and hours.
if (1 == dwDays)
{
if (1 == dwHours)
LoadString(MLGetHinst(), IDS_TIMEEST_DAYHOUR, szTemplate, ARRAYSIZE(szTemplate));
else
LoadString(MLGetHinst(), IDS_TIMEEST_DAYHOURS, szTemplate, ARRAYSIZE(szTemplate));
}
else
{
if (1 == dwHours)
LoadString(MLGetHinst(), IDS_TIMEEST_DAYSHOUR, szTemplate, ARRAYSIZE(szTemplate));
else
LoadString(MLGetHinst(), IDS_TIMEEST_DAYSHOURS, szTemplate, ARRAYSIZE(szTemplate));
}
_FormatMessageWrapper(szTemplate, dwDays, dwHours, pszOut, cchSize);
}
else
{
// It's let than a day, so display hours and minutes.
dwMinutes %= TIME_MINUTES_IN_HOUR;
// It's more than a day, so display days and hours.
if (1 == dwHours)
{
if (1 == dwMinutes)
LoadString(MLGetHinst(), IDS_TIMEEST_HOURMIN, szTemplate, ARRAYSIZE(szTemplate));
else
LoadString(MLGetHinst(), IDS_TIMEEST_HOURMINS, szTemplate, ARRAYSIZE(szTemplate));
}
else
{
if (1 == dwMinutes)
LoadString(MLGetHinst(), IDS_TIMEEST_HOURSMIN, szTemplate, ARRAYSIZE(szTemplate));
else
LoadString(MLGetHinst(), IDS_TIMEEST_HOURSMINS, szTemplate, ARRAYSIZE(szTemplate));
}
_FormatMessageWrapper(szTemplate, dwHours, dwMinutes, pszOut, cchSize);
}
}
// This sets the "Seconds Left" text in the progress dialog
void CProgressDialog::_SetProgressTimeEst(DWORD dwSecondsLeft)
{
TCHAR szFmt[CCH_TIMET_TEMPLATE_SIZE];
TCHAR szOut[CCH_TIME_SIZE];
DWORD dwTime;
DWORD dwTickCount = GetTickCount();
// Since the progress time has either a 1 minute or 5 second granularity (depending on whether the total time
// remaining is greater or less than 1 minute), we only update it every 20 seconds if the total time is > 1 minute,
// and ever 4 seconds if the time is < 1 minute. This keeps the time from flashing back and forth between
// boundaries (eg 45 secondsand 40 seconds remaining).
if (dwTickCount - _dwLastUpdatedTimeRemaining < (DWORD)((dwSecondsLeft > 60) ? 20000 : 4000))
return;
if (_fNoTime)
{
szOut[0] = TEXT('\0');
}
else
{
// Is it more than an hour?
if (dwSecondsLeft > (TIME_SECONDS_IN_MINUTE * TIME_MINUTES_IN_HOUR))
_SetProgressLargeTimeEst(dwSecondsLeft, szOut, ARRAYSIZE(szOut));
else
{
// No.
if (dwSecondsLeft > TIME_SECONDS_IN_MINUTE)
{
// Note that dwTime is at least 2, so we only need a plural form
LoadString(MLGetHinst(), IDS_TIMEEST_MINUTES, szFmt, ARRAYSIZE(szFmt));
dwTime = (dwSecondsLeft / TIME_SECONDS_IN_MINUTE) + 1;
}
else
{
LoadString(MLGetHinst(), IDS_TIMEEST_SECONDS, szFmt, ARRAYSIZE(szFmt));
// Round up to 5 seconds so it doesn't look so random
dwTime = ((dwSecondsLeft + 4) / 5) * 5;
}
wnsprintf(szOut, ARRAYSIZE(szOut), szFmt, dwTime);
}
}
// we are updating now, so set the _dwLastUpdatedTimeRemaining to now
_dwLastUpdatedTimeRemaining = dwTickCount;
// update the Time remaining field
SetDlgItemText(_hwndProgress, IDD_PROGDLG_LINE3, szOut);
}
#define MAX(x, y) ((x) > (y) ? (x) : (y))
//
// This function updates the ProgressTime field (aka Line3)
//
HRESULT CProgressDialog::_SetProgressTime(void)
{
DWORD dwSecondsLeft;
DWORD dwTotal;
DWORD dwCompleted;
DWORD dwCurrentRate;
DWORD dwTickDelta;
DWORD dwLeft;
DWORD dwCurrentTickCount;
_iNumTimesSetProgressCalled++;
// grab these in the crit sec (because they can change, and we need a matched set)
ENTERCRITICAL;
dwTotal = _dwTotal;
dwCompleted = _dwCompleted;
dwCurrentTickCount = _dwLastUpdatedTickCount;
LEAVECRITICAL;
dwLeft = dwTotal - dwCompleted;
dwTickDelta = dwCurrentTickCount - _dwPrevTickCount;
if (!dwTotal || !dwCompleted)
return dwTotal ? S_FALSE : E_FAIL;
// we divide the TickDelta by 100 to give tenths of seconds, so if we have recieved an
// update faster than that, just skip it
if (dwTickDelta < 100)
{
return S_FALSE;
}
TraceMsg(TF_PROGRESS, "Current tick count = %lu", dwCurrentTickCount);
TraceMsg(TF_PROGRESS, "Total work = %lu", dwTotal);
TraceMsg(TF_PROGRESS, "Completed work = %lu", dwCompleted);
TraceMsg(TF_PROGRESS, "Prev. comp work= %lu", _dwPrevCompleted);
TraceMsg(TF_PROGRESS, "Work left = %lu", dwLeft);
TraceMsg(TF_PROGRESS, "Tick delta = %lu", dwTickDelta);
if (dwTotal < dwCompleted)
{
// we can get into this case if we are applying attributes to sparse files
// on a volume. As we add up the file sizes, we end up with a number that is bigger
// than the drive size. We get rid of the time so that we wont show the user something
// completely bogus
_fNoTime = TRUE;
dwTotal = dwCompleted + (dwCompleted >> 3); // fudge dwTotal forward a bit
TraceMsg(TF_PROGRESS, "!! (Total < Completed), fudging Total work to = %lu", dwTotal);
}
if(dwCompleted <= _dwPrevCompleted)
{
// woah, we are going backwards, we dont deal w/ negative or zero rates so...
dwCurrentRate = (_dwPrevRate ? _dwPrevRate : 2);
}
else
{
// calculate the current rate in points per tenth of a second
dwTickDelta /= 100;
if (0 == dwTickDelta)
dwTickDelta = 1; // Protect from divide by zero
dwCurrentRate = (dwCompleted - _dwPrevCompleted) / dwTickDelta;
}
TraceMsg(TF_PROGRESS, "Current rate = %lu", dwCurrentRate);
TraceMsg(TF_PROGRESS, "Prev. rate = %lu", _dwPrevRate);
// time remaining in seconds (we take a REAL average to smooth out random fluxuations)
DWORD dwAverageRate = (DWORD)((dwCurrentRate + (_int64)_dwPrevRate * _iNumTimesSetProgressCalled) / (_iNumTimesSetProgressCalled + 1));
TraceMsg(TF_PROGRESS, "Average rate= %lu", dwAverageRate);
dwAverageRate = MAX(dwAverageRate, 1); // Protect from divide by zero
dwSecondsLeft = (dwLeft / dwAverageRate) / 10;
TraceMsg(TF_PROGRESS, "Seconds left = %lu", dwSecondsLeft);
TraceMsg(TF_PROGRESS, "");
// It would be odd to show "1 second left" and then immediately clear it, and to avoid showing
// rediculous early estimates, we dont show anything until we have at least 5 data points
if ((dwSecondsLeft >= MIN_MINTIME4FEEDBACK) && (_iNumTimesSetProgressCalled >= 5))
{
// display new estimate of time left
_SetProgressTimeEst(dwSecondsLeft);
}
// set all the _dwPrev stuff for next time
_dwPrevRate = dwAverageRate;
_dwPrevTickCount = dwCurrentTickCount;
_dwPrevCompleted = dwCompleted;
return S_OK;
}
INT_PTR CALLBACK CProgressDialog::ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
CProgressDialog * ppd = (CProgressDialog *)GetWindowLongPtr(hDlg, DWLP_USER);
if (WM_INITDIALOG == wMsg)
{
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
ppd = (CProgressDialog *)lParam;
}
if (ppd)
return ppd->_ProgressDialogProc(hDlg, wMsg, wParam, lParam);
return DefWindowProc(hDlg, wMsg, wParam, lParam);
}
// apithk.c entry
STDAPI_(void) ProgressSetMarqueeMode(HWND hwndProgress, BOOL bOn);
void CProgressDialog::_ShowProgressBar(HWND hwnd)
{
if (hwnd)
{
HWND hwndPrgress = GetDlgItem(hwnd, IDD_PROGDLG_PROGRESSBAR);
ProgressSetMarqueeMode(hwndPrgress, (SPBEGINF_MARQUEEPROGRESS & _spbeginf));
UINT swp = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
if (SPBEGINF_NOPROGRESSBAR & _spbeginf)
swp |= SWP_HIDEWINDOW;
else
swp |= SWP_SHOWWINDOW;
SetWindowPos(hwndPrgress, NULL, 0, 0, 0, 0, swp);
}
}
BOOL CProgressDialog::_OnInit(HWND hDlg)
{
// dont minimize if the caller requests or is modal
if ((SPINITF_MODAL | SPINITF_NOMINIMIZE) & _spinitf)
{
// The caller wants us to remove the Minimize Box or button in the caption bar.
SHSetWindowBits(hDlg, GWL_STYLE, WS_MINIMIZEBOX, 0);
}
_ShowProgressBar(hDlg);
return FALSE;
}
BOOL CProgressDialog::_ProgressDialogProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
BOOL fHandled = TRUE; // handled
switch (wMsg)
{
case WM_INITDIALOG:
return _OnInit(hDlg);
case WM_SHOWWINDOW:
if (wParam)
{
_SetModeless(FALSE);
ASSERT(_hwndProgress);
SetAnimation(_hInstAnimation, _idAnimation);
// set the initial text values
if (_pwzTitle)
SetTitle(_pwzTitle);
if (_pwzLine1)
SetLine(1, _pwzLine1, FALSE, NULL);
if (_pwzLine2)
SetLine(2, _pwzLine2, FALSE, NULL);
if (_pwzLine3)
SetLine(3, _pwzLine3, FALSE, NULL);
}
break;
case WM_DESTROY:
_SetModeless(TRUE);
if (_hwndDlgParent)
{
if (SHIsChildOrSelf(_hwndProgress, GetFocus()))
SetForegroundWindow(_hwndDlgParent);
}
break;
case WM_ENABLE:
if (wParam)
{
// we assume that we were previously disabled and thus restart our tick counter
// because we also naively assume that no work was being done while we were disabled
_dwPrevTickCount = GetTickCount();
}
_PauseAnimation(wParam == 0);
break;
case WM_TIMER:
if (wParam == ID_SHOWTIMER)
{
KillTimer(hDlg, ID_SHOWTIMER);
_DisplayDialog();
_dwFirstShowTime = GetTickCount();
}
break;
case WM_COMMAND:
if (IDCANCEL == GET_WM_COMMAND_ID(wParam, lParam))
_UserCancelled();
break;
case PDM_SHUTDOWN:
// Make sure this window is shown before telling the user there
// is a problem. Ignore FOF_NOERRORUI here because of the
// nature of the situation
MLShellMessageBox(hDlg, MAKEINTRESOURCE(IDS_CANTSHUTDOWN), NULL, (MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND));
break;
case PDM_TERMTHREAD:
// a dummy id that we can take so that folks can post to us and make
// us go through the main loop
break;
case WM_SYSCOMMAND:
switch(wParam)
{
case SC_MINIMIZE:
_fMinimized = TRUE;
break;
case SC_RESTORE:
SetTitle(_pwzTitle); // Restore title to original text.
_fMinimized = FALSE;
break;
}
fHandled = FALSE;
break;
case PDM_UPDATE:
if (!_fCancel && IsWindowEnabled(hDlg))
{
_SetProgressTime();
_UpdateProgressDialog();
}
// we are done processing the update
_fChangePosted = FALSE;
break;
case WM_QUERYENDSESSION:
// Post a message telling the dialog to show the "We can't shutdown now"
// dialog and return to USER right away, so we don't have to worry about
// the user not clicking the OK button before USER puts up its "this
// app didn't respond" dialog
PostMessage(hDlg, PDM_SHUTDOWN, 0, 0);
// Make sure the dialog box procedure returns FALSE
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
return(TRUE);
default:
fHandled = FALSE; // Not handled
}
return fHandled;
}
// This is used to asyncronously update the progess dialog.
void CProgressDialog::_AsyncUpdate(void)
{
if (!_fChangePosted && _hwndProgress) // Prevent from posting too many messages.
{
// set the flag first because with async threads
// the progress window could handle it and clear the
// bit before we set it.. then we'd lose further messages
// thinking that one was still pending
_fChangePosted = TRUE;
if (!PostMessage(_hwndProgress, PDM_UPDATE, 0, 0))
{
_fChangePosted = FALSE;
}
}
}
void CProgressDialog::_UpdateProgressDialog(void)
{
if (_fTotalChanged)
{
_fTotalChanged = FALSE;
if (0x80000000 & _dwTotal)
_fScaleBug = TRUE;
SendMessage(GetDlgItem(_hwndProgress, IDD_PROGDLG_PROGRESSBAR), PBM_SETRANGE32, 0, (_fScaleBug ? (_dwTotal >> 1) : _dwTotal));
}
if (_fCompletedChanged)
{
_fCompletedChanged = FALSE;
SendMessage(GetDlgItem(_hwndProgress, IDD_PROGDLG_PROGRESSBAR), PBM_SETPOS, (WPARAM) (_fScaleBug ? (_dwCompleted >> 1) : _dwCompleted), 0);
}
}
void CProgressDialog::_PauseAnimation(BOOL bStop)
{
// only called from within the hwndProgress wndproc so assum it's there
if (_hwndProgress)
{
if (bStop)
{
Animate_Stop(GetDlgItem(_hwndProgress, IDD_PROGDLG_ANIMATION));
}
else
{
Animate_Play(GetDlgItem(_hwndProgress, IDD_PROGDLG_ANIMATION), -1, -1, -1);
}
}
}
void CProgressDialog::_UserCancelled(void)
{
// Don't hide the dialog because the caller may not pole
// ::HasUserCancelled() for quite a while.
// ShowWindow(hDlg, SW_HIDE);
_fCancel = TRUE;
// give minimal feedback that the cancel click was accepted
EnableWindow(GetDlgItem(_hwndProgress, IDCANCEL), FALSE);
// If the user cancels, Line 1 & 2 will be cleared and Line 3 will get this msg.
if (!_pwzCancelMsg)
{
WCHAR wzDefaultMsg[MAX_PATH];
LoadStringW(MLGetHinst(), IDS_DEFAULT_CANCELPROG, wzDefaultMsg, ARRAYSIZE(wzDefaultMsg));
Str_SetPtr(&_pwzCancelMsg, wzDefaultMsg);
}
SetLine(1, L"", FALSE, NULL);
SetLine(2, L"", FALSE, NULL);
SetLine(3, _pwzCancelMsg, FALSE, NULL);
}
HRESULT CProgressDialog::Initialize(SPINITF flags, LPCWSTR pszTitle, LPCWSTR pszCancel)
{
if (!_fInitialized)
{
_spinitf = flags;
if (pszTitle)
SetTitle(pszTitle);
if (pszCancel)
SetCancelMsg(pszCancel, NULL);
_fInitialized = TRUE;
return S_OK;
}
return E_UNEXPECTED;
}
void CProgressDialog::_SetModeless(BOOL fModeless)
{
// if the user is requesting a modal window, disable the parent now.
if (_spinitf & SPINITF_MODAL)
{
if (FAILED(IUnknown_EnableModless(_punkSite, fModeless))
&& _hwndDlgParent)
{
EnableWindow(_hwndDlgParent, fModeless);
}
}
}
HRESULT CProgressDialog::_BeginAction(SPBEGINF flags)
{
_spbeginf = flags;
_fTermThread = FALSE;
_fTotalChanged = TRUE;
if (!_fThreadRunning)
{
SHCreateThread(CProgressDialog::ThreadProc, this, CTF_FREELIBANDEXIT, CProgressDialog::SyncThreadProc);
// _fThreadRunning is set in _SyncThreadProc()
}
if (_fThreadRunning)
{
_fInAction = TRUE;
_ShowProgressBar(_hwndProgress);
// initialize the _dwPrev counters
_dwPrevRate = 0;
_dwPrevCompleted = 0;
_dwPrevTickCount = GetTickCount();
TraceMsg(TF_PROGRESS, "Initial tick count = %lu", _dwPrevTickCount);
return S_OK;
}
return E_OUTOFMEMORY;
}
#define ACTIONENTRY(a, dll, id) {a, dll, id}
#define c_szShell32 "shell32.dll"
#define c_szShdocvw "shdocvw.dll"
const static struct
{
SPACTION action;
LPCSTR pszDll;
UINT id;
}
c_spActions[] =
{
ACTIONENTRY(SPACTION_MOVING, c_szShell32, 160), // IDA_FILEMOVE
ACTIONENTRY(SPACTION_COPYING, c_szShell32, 161), // IDA_FILECOPY
ACTIONENTRY(SPACTION_RECYCLING, c_szShell32, 162), // IDA_FILEDEL
ACTIONENTRY(SPACTION_APPLYINGATTRIBS, c_szShell32, 165), // IDA_APPLYATTRIBS
ACTIONENTRY(SPACTION_DOWNLOADING, c_szShdocvw, 0x100),
ACTIONENTRY(SPACTION_SEARCHING_INTERNET, c_szShell32, 166), // IDA_ISEARCH
ACTIONENTRY(SPACTION_SEARCHING_FILES, c_szShell32, 150) // IDA_SEARCH
};
HRESULT CProgressDialog::Begin(SPACTION action, SPBEGINF flags)
{
if (_fInAction || !_fInitialized)
return E_FAIL;
HRESULT hr = S_OK;
for (int i = 0; i < ARRAYSIZE(c_spActions); i++)
{
if (c_spActions[i].action == action)
{
HINSTANCE hinst = LoadLibraryA(c_spActions[i].pszDll);
if (hinst)
{
hr = SetAnimation(hinst, c_spActions[i].id);
if (_hinstFree)
FreeLibrary(_hinstFree);
_hinstFree = hinst;
}
break;
}
}
if (SUCCEEDED(hr))
{
if (!_hwndDlgParent)
IUnknown_GetWindow(_punkSite, &_hwndDlgParent);
hr = _BeginAction(flags);
}
return hr;
}
#define SPINIT_MASK (SPINITF_MODAL | SPINITF_NOMINIMIZE)
#define SPBEGIN_MASK 0x1F
// IProgressDialog
HRESULT CProgressDialog::StartProgressDialog(HWND hwndParent, IUnknown * punkNotUsed, DWORD dwFlags, LPCVOID pvResevered)
{
if (_fInAction)
return S_OK;
HRESULT hr = Initialize(dwFlags & SPINIT_MASK, NULL, NULL);
if (SUCCEEDED(hr))
{
_fNoTime = dwFlags & PROGDLG_NOTIME;
// we dont Save punkNotUsed
_hwndDlgParent = hwndParent;
hr = _BeginAction(dwFlags & SPBEGIN_MASK);
}
return hr;
}
HRESULT CProgressDialog::End()
{
ASSERT(_fInitialized && _fInAction);
// possibly need to pop stack or change state
_fInAction = FALSE;
_spbeginf = 0;
return S_OK;
}
HRESULT CProgressDialog::Stop()
{
ASSERT(!_fInAction);
BOOL fFocusParent = FALSE;
// shut down the progress dialog
if (_fThreadRunning)
{
ASSERT(_hwndProgress);
_fTermThread = TRUE;
PostMessage(_hwndProgress, PDM_TERMTHREAD, 0, 0);
}
return S_OK;
}
HRESULT CProgressDialog::StopProgressDialog(void)
{
// callers can call this over and over
if (_fInAction)
End();
return Stop();
}
HRESULT CProgressDialog::SetTitle(LPCWSTR pwzTitle)
{
HRESULT hr = S_OK;
// Does the dialog exist?
if (_hwndProgress)
{
// Yes, so put the value directly into the dialog.
if (!SetWindowTextW(_hwndProgress, (pwzTitle ? pwzTitle : L"")))
hr = E_FAIL;
}
else
Str_SetPtrW(&_pwzTitle, pwzTitle);
return hr;
}
HRESULT CProgressDialog::SetAnimation(HINSTANCE hInstAnimation, UINT idAnimation)
{
HRESULT hr = S_OK;
_hInstAnimation = hInstAnimation;
_idAnimation = idAnimation;
// Does the dialog exist?
if (_hwndProgress)
{
if (!Animate_OpenEx(GetDlgItem(_hwndProgress, IDD_PROGDLG_ANIMATION), _hInstAnimation, IntToPtr(_idAnimation)))
hr = E_FAIL;
}
return hr;
}
HRESULT CProgressDialog::UpdateText(SPTEXT sptext, LPCWSTR pszText, BOOL fMayCompact)
{
if (_fInitialized)
return SetLine((DWORD)sptext, pszText, fMayCompact, NULL);
else
return E_UNEXPECTED;
}
HRESULT CProgressDialog::SetLine(DWORD dwLineNum, LPCWSTR pwzString, BOOL fCompactPath, LPCVOID pvResevered)
{
HRESULT hr = E_INVALIDARG;
switch (dwLineNum)
{
case 1:
hr = _SetLineHelper(pwzString, &_pwzLine1, IDD_PROGDLG_LINE1, fCompactPath);
break;
case 2:
hr = _SetLineHelper(pwzString, &_pwzLine2, IDD_PROGDLG_LINE2, fCompactPath);
break;
case 3:
if (_spbeginf & SPBEGINF_AUTOTIME)
{
// you cant change line3 directly if you want PROGDLG_AUTOTIME, because
// this is updated by the progress dialog automatically
// unless we're cancelling
ASSERT(_fCancel);
hr = _fCancel ? S_OK : E_INVALIDARG;
break;
}
hr = _SetLineHelper(pwzString, &_pwzLine3, IDD_PROGDLG_LINE3, fCompactPath);
break;
default:
ASSERT(0);
}
return hr;
}
HRESULT CProgressDialog::SetCancelMsg(LPCWSTR pwzCancelMsg, LPCVOID pvResevered)
{
Str_SetPtr(&_pwzCancelMsg, pwzCancelMsg); // If the user cancels, Line 1 & 2 will be cleared and Line 3 will get this msg.
return S_OK;
}
HRESULT CProgressDialog::Timer(DWORD dwAction, LPCVOID pvResevered)
{
HRESULT hr = E_NOTIMPL;
switch (dwAction)
{
case PDTIMER_RESET:
_dwPrevTickCount = GetTickCount();
hr = S_OK;
break;
}
return hr;
}
HRESULT CProgressDialog::SetProgress(DWORD dwCompleted, DWORD dwTotal)
{
DWORD dwTickCount = GetTickCount(); // get the tick count before taking the critical section
// we grab the crit section in case the UI thread is trying to access
// _dwCompleted, _dwTotal or _dwLastUpdatedTickCount to do its time update.
ENTERCRITICAL;
if (_dwCompleted != dwCompleted)
{
_dwCompleted = dwCompleted;
_fCompletedChanged = TRUE;
}
if (_dwTotal != dwTotal)
{
_dwTotal = dwTotal;
_fTotalChanged = TRUE;
}
if (_fCompletedChanged || _fTotalChanged)
{
_dwLastUpdatedTickCount = dwTickCount;
}
LEAVECRITICAL;
#ifdef DEBUG
if (_dwCompleted > _dwTotal)
{
TraceMsg(TF_WARNING, "CProgressDialog::SetProgress(_dwCompleted > _dwTotal ?!?!)");
}
#endif
if (_fCompletedChanged || _fTotalChanged)
{
// something changed, so update the progress dlg
_AsyncUpdate();
}
TraceMsg(TF_PROGRESS, "CProgressDialog::SetProgress(Complete=%#08lx, Total=%#08lx)", dwCompleted, dwTotal);
if (_fMinimized)
{
_SetTitleBarProgress(dwCompleted, dwTotal);
}
return S_OK;
}
HRESULT CProgressDialog::UpdateProgress(ULONGLONG ulCompleted, ULONGLONG ulTotal)
{
if (_fInitialized && _fInAction)
return SetProgress64(ulCompleted, ulTotal);
else
return E_UNEXPECTED;
}
HRESULT CProgressDialog::SetProgress64(ULONGLONG ullCompleted, ULONGLONG ullTotal)
{
ULARGE_INTEGER uliCompleted, uliTotal;
uliCompleted.QuadPart = ullCompleted;
uliTotal.QuadPart = ullTotal;
// If we are using the top 32 bits, scale both numbers down.
// Note that I'm using the attribute that dwTotalHi is always
// larger than dwCompletedHi
ASSERT(uliTotal.HighPart >= uliCompleted.HighPart);
while (uliTotal.HighPart)
{
uliCompleted.QuadPart >>= 1;
uliTotal.QuadPart >>= 1;
}
ASSERT((0 == uliCompleted.HighPart) && (0 == uliTotal.HighPart)); // Make sure we finished scaling down.
return SetProgress(uliCompleted.LowPart, uliTotal.LowPart);
}
HRESULT CProgressDialog::_SetTitleBarProgress(DWORD dwCompleted, DWORD dwTotal)
{
TCHAR szTemplate[MAX_PATH];
TCHAR szTitle[MAX_PATH];
int nPercent = 0;
if (dwTotal) // Disallow divide by zero.
{
// Will scaling it up cause a wrap?
if ((100 * 100) <= dwTotal)
{
// Yes, so scale down.
nPercent = (dwCompleted / (dwTotal / 100));
}
else
{
// No, so scale up.
nPercent = ((100 * dwCompleted) / dwTotal);
}
}
LoadString(MLGetHinst(), IDS_TITLEBAR_PROGRESS, szTemplate, ARRAYSIZE(szTemplate));
wnsprintf(szTitle, ARRAYSIZE(szTitle), szTemplate, nPercent);
SetWindowText(_hwndProgress, szTitle);
return S_OK;
}
HRESULT CProgressDialog::ResetCancel()
{
_fCancel = FALSE;
if (_pwzLine1)
SetLine(1, _pwzLine1, FALSE, NULL);
if (_pwzLine2)
SetLine(2, _pwzLine2, FALSE, NULL);
if (_pwzLine3)
SetLine(3, _pwzLine3, FALSE, NULL);
return S_OK;
}
HRESULT CProgressDialog::QueryCancel(BOOL * pfCancelled)
{
*pfCancelled = HasUserCancelled();
return S_OK;
}
/****************************************************\
DESCRIPTION:
This queries the progress dialog for a cancel and yields.
it also will show the progress dialog if a certain amount of time has passed
returns:
TRUE cacnel was pressed, abort the operation
FALSE continue
\****************************************************/
BOOL CProgressDialog::HasUserCancelled(void)
{
if (!_fCancel && _hwndProgress)
{
MSG msg;
// win95 handled messages in here.
// we need to do the same in order to flush the input queue as well as
// for backwards compatability.
// we need to flush the input queue now because hwndProgress is
// on a different thread... which means it has attached thread inputs
// inorder to unlock the attached threads, we need to remove some
// sort of message until there's none left... any type of message..
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (!IsDialogMessage(_hwndProgress, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
if (_fTotalChanged || _fCompletedChanged)
_AsyncUpdate();
}
return _fCancel;
}
// IOleWindow
HRESULT CProgressDialog::GetWindow(HWND * phwnd)
{
HRESULT hr = E_FAIL;
*phwnd = _hwndProgress;
if (_hwndProgress)
hr = S_OK;
return hr;
}
HRESULT CProgressDialog::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CProgressDialog, IProgressDialog),
QITABENT(CProgressDialog, IActionProgressDialog),
QITABENT(CProgressDialog, IActionProgress),
QITABENT(CProgressDialog, IObjectWithSite),
QITABENT(CProgressDialog, IOleWindow),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
ULONG CProgressDialog::AddRef(void)
{
return InterlockedIncrement(&_cRef);
}
ULONG CProgressDialog::Release(void)
{
if (InterlockedDecrement(&_cRef))
return _cRef;
if (_fThreadRunning)
{
// need to keep this thread's ref around
// for a while longer to avoid the race
// to destroy this object on the dialog thread
AddRef();
ENTERCRITICAL;
if (_fThreadRunning)
{
// we call addref
AddRef();
_fReleaseSelf = TRUE;
}
LEAVECRITICAL;
Stop();
Release();
}
else
delete this;
return 0;
}
CProgressDialog::CProgressDialog() : _cRef(1)
{
DllAddRef();
// ASSERT zero initialized because we can only be created in the heap. (Private destructor)
ASSERT(!_pwzLine1);
ASSERT(!_pwzLine2);
ASSERT(!_pwzLine3);
ASSERT(!_fCancel);
ASSERT(!_fTermThread);
ASSERT(!_fInAction);
ASSERT(!_hwndProgress);
ASSERT(!_hwndDlgParent);
ASSERT(!_fChangePosted);
ASSERT(!_dwLastUpdatedTimeRemaining);
ASSERT(!_dwCompleted);
ASSERT(!_fCompletedChanged);
ASSERT(!_fTotalChanged);
ASSERT(!_fMinimized);
_dwTotal = 1; // Init to Completed=0, Total=1 so we are at 0%.
}
CProgressDialog::~CProgressDialog()
{
ASSERT(!_fInAction);
ASSERT(!_fThreadRunning);
Str_SetPtrW(&_pwzTitle, NULL);
Str_SetPtrW(&_pwzLine1, NULL);
Str_SetPtrW(&_pwzLine2, NULL);
Str_SetPtrW(&_pwzLine3, NULL);
if (_hinstFree)
FreeLibrary(_hinstFree);
DllRelease();
}
STDAPI CProgressDialog_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
// aggregation checking is handled in class factory
*ppunk = NULL;
CProgressDialog * pProgDialog = new CProgressDialog();
if (pProgDialog)
{
*ppunk = SAFECAST(pProgDialog, IProgressDialog *);
return S_OK;
}
return E_OUTOFMEMORY;
}