/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include "MyProgressCtrl.h" #include "MyMemoryDC.h" #include "Generic.h" #include "MyApp.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define MAX_HLS 240 // Maximum value for Hue, Luminence, and Saturation. ///////////////////////////////////////////////////////////////////////////// // MyProgressCtrl // Constructor. MyProgressCtrl::MyProgressCtrl(bool bRandomizeColors /* = false */ ) : m_nLower(0) , m_nUpper(100) , m_nCurrentPosition(0) , m_nStep(1) , m_crBeg(RGB_RED) , m_crEnd(RGB_BLUE) , m_crBkGround(::GetSysColor(COLOR_3DFACE)) , m_crText(RGB_WHITE) , m_bShowMessage(true) , m_pfntText(NULL) , m_dwTickStart(0) , m_dPercentLast(0.0) , m_bSetEtaStartCalled(false) { HRESULT hr(S_OK); // Be sure that the random number generator has been seeded. (If the // calling app has already done this, this does no harm.) srand((unsigned) time(NULL)); // Randomly set colors. We want well saturated, non-grey, far apart colors. if (bRandomizeColors) { RandomizeColors(); } // Create font. m_pfntText = new CFont; ASSERT_VALID(m_pfntText); if (m_pfntText) { EC_B(m_pfntText->CreatePointFont(10 * 10, "Arial")); } } // Destructor. /* virtual */ MyProgressCtrl::~MyProgressCtrl() { delete m_pfntText; m_pfntText = NULL; } BEGIN_MESSAGE_MAP(MyProgressCtrl, CProgressCtrl) //{{AFX_MSG_MAP(MyProgressCtrl) ON_WM_PAINT() ON_WM_ERASEBKGND() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyProgressCtrl message handlers // The main drawing routine. Consists of two parts: // // (1) Call the DrawGradient routine to draw the visible part of the // progress gradient. // // (2) If needed, show the percentage text. // // The way that the ETA is implemented means that the very first increment of // the progress bar will not show the ETA (only the percentage). If this is a // problem, just call SetProgressStart() when processing is first begun. void MyProgressCtrl::OnPaint() { VALIDATE; _ASSERTE(0 < m_nUpper); HRESULT hr(S_OK); // Create the DC and select the font into it. CPaintDC dc(this); CFont* pfntOld = dc.SelectObject(m_pfntText); // If the current position is invalid, we should fade into the background. if (m_nCurrentPosition < m_nLower || m_nCurrentPosition > m_nUpper) { DOMYLOGW ("MyProgressCtrl::OnPaint(): Current position <%d> is " "invalid.\n", m_nCurrentPosition); CRect rc; GetClientRect(rc); CBrush brush; brush.CreateSolidBrush(::GetSysColor(COLOR_3DFACE)); dc.FillRect(&rc, &brush); EC_B(brush.DeleteObject()); } else { // Figure out what part should be visible so we can stop the gradient // when needed. CRect rcClient; GetClientRect(rcClient); int nMaxWidth((int) (((float) m_nCurrentPosition / (float) m_nUpper * (float) rcClient.right))); // Draw the gradient. DrawGradient(dc, rcClient, nMaxWidth); // Show percent indicator and ETA if requested. DrawMessage(dc, rcClient); } // Deselect the font. EC_V_(dc.SelectObject(pfntOld)); // Do not call CProgressCtrl::OnPaint() for painting messages } // Need to keep track of where the indicator thinks it is. /* virtual */ void MyProgressCtrl::SetRange(int nLower, int nUpper) { VALIDATE; _ASSERTE(nLower <= nUpper); _ASSERTE(0 < nUpper); if (nLower <= nUpper && 0 < nUpper) { m_nLower = nLower; m_nUpper = nUpper; m_nCurrentPosition = nLower; CProgressCtrl::SetRange(nLower, nUpper); } } // Need to keep track of where the indicator thinks it is. /* virtual */ int MyProgressCtrl::SetStep(int nStep) { VALIDATE; m_nStep = nStep; return CProgressCtrl::SetStep(nStep); } // Need to keep track of where the indicator thinks it is. /* virtual */ int MyProgressCtrl::SetPos(int nPos, bool bInvalidate /* = true */ ) { VALIDATE; _ASSERTE(nPos <= m_nUpper); m_nCurrentPosition = nPos; if (bInvalidate) Invalidate(); return CProgressCtrl::SetPos(m_nCurrentPosition); } // Need to keep track of where the indicator thinks it is. /* virtual */ int MyProgressCtrl::SetPos(double dPos, bool bInvalidate /* = true */ ) { VALIDATE; _ASSERTE(static_cast (dPos) <= m_nUpper); m_nCurrentPosition = static_cast (dPos); if (bInvalidate) Invalidate(); return CProgressCtrl::SetPos(m_nCurrentPosition); } // Need to keep track of where the indicator thinks it is. /* virtual */ int MyProgressCtrl::StepIt(bool bInvalidate /* = true */ ) { VALIDATE; m_nCurrentPosition += m_nStep; if (bInvalidate) Invalidate(); return CProgressCtrl::StepIt(); } // Where most of the actual work is done. The general version would fill the // entire rectangle with a gradient, but we want to truncate the drawing to // reflect the actual progress control position. void MyProgressCtrl::DrawGradient(CPaintDC& dc, const CRect& rcClient, int nMaxWidth) { VALIDATE; HRESULT hr(S_OK); CRect rc; VERIFY(ERROR != dc.GetClipBox(&rc)); MyMemoryDC memDC(&dc, rc); // First find out the largest color distance between the start and end // colors. This distance will determine how many steps we use to carve up // the client region and the size of each gradient rect. First distance, // then starting value. int nR(GetRValue(m_crEnd) - GetRValue(m_crBeg)); int nG(GetGValue(m_crEnd) - GetGValue(m_crBeg)); int nB(GetBValue(m_crEnd) - GetBValue(m_crBeg)); // Make the number of steps equal to the greatest distance. int nSteps(max(abs(nR), max(abs(nG), abs(nB)))); // Determine how large each band should be in order to cover the client with // nSteps bands (one for every color intensity level). float fStep((float) rcClient.right / (float) nSteps); // Calculate the step size for each color. float fRStep(nR / (float) nSteps); float fGStep(nG / (float) nSteps); float fBStep(nB / (float) nSteps); // Reset the colors to the starting position. nR = GetRValue(m_crBeg); nG = GetGValue(m_crBeg); nB = GetBValue(m_crBeg); // Start filling bands. for (int nOnBand = 0; nOnBand < nSteps; nOnBand++) { CRect rcFill((int) ((float) nOnBand * fStep), 0, min(nMaxWidth, (int) ((float) (nOnBand + 1) * fStep)), rcClient.bottom + 1); // CDC::FillSolidRect is faster, but it does not handle 8-bit color // depth. CBrush brush; EC_B(brush.CreateSolidBrush(RGB(nR + fRStep * nOnBand, nG + fGStep * nOnBand, nB + fBStep * nOnBand))); EC_V(memDC.FillRect(rcFill, &brush)); EC_B_(brush.DeleteObject()); // If we are past the maximum for the current position we need to get // out of the loop. Before we leave, we repaint the remainder of the // client area with the background color. if ((nOnBand + 1) * fStep > nMaxWidth) { rcFill.SetRect(rcFill.right, 0, rcClient.right, rcClient.bottom); EC_B(brush.CreateSolidBrush(m_crBkGround)); EC_V(memDC.FillRect(rcFill, &brush)); EC_B_(brush.DeleteObject()); break; } } } // All drawing is done in the OnPaint function. BOOL MyProgressCtrl::OnEraseBkgnd(CDC* pDC) { VALIDATE; UNUSED_ALWAYS(pDC); return TRUE; } // Pick random colors. void MyProgressCtrl::RandomizeColors() { VALIDATE; int nH(rand() % MAX_HLS); int nL(MAX_HLS / 2); int nS((3 * MAX_HLS / 4) + (rand() % (MAX_HLS / 4))); m_crBeg = Generic::HLStoRGB(static_cast (nH), static_cast (nL), static_cast (nS)); DOMYLOGD (" Beg HLS: (%d,%d,%d)\n", nH, nL, nS); nH += (MAX_HLS / 4) + (rand() % (MAX_HLS / 2)); nH = nH % (MAX_HLS + 1); nS = (3 * MAX_HLS / 4) + (rand() % (MAX_HLS / 4)); m_crEnd = Generic::HLStoRGB(static_cast (nH), static_cast (nL), static_cast (nS)); DOMYLOGD (" Beg HLS: (%d,%d,%d)\n", nH, nL, nS); } // Draw percent complete and ETA. Note that since we can't set the Start tick // reliably here (as it would get set the first time the control was painted, // not when processing started) the first increment won't show the ETA. void MyProgressCtrl::DrawMessage(CDC& dc, CRect& rcClient) { VALIDATE; ASSERT_VALID(&dc); if (m_bShowMessage && 0 < m_nUpper) { // Figure the percent. double dPercentThis(100.0 * (double) m_nCurrentPosition / (double) m_nUpper); CString sPercent; sPercent.Format("%.1f%%",dPercentThis); // If the percent has changed (or this is the first time), build a new // text message; otherwise we can use the old one. if (0.0 == dPercentThis || m_dPercentLast != dPercentThis) { // Figure the ETA (if SetEtaStart() was called - otherwise just // display the percentage. DWORD dwTickThis(GetTickCount()); CString sEta; if (0.0 < dPercentThis && 100.0 > dPercentThis && m_dwTickStart < dwTickThis) { if (m_bSetEtaStartCalled) { DWORD dwElapMS(dwTickThis - m_dwTickStart); int nEtaMs(((100 * dwElapMS) / dPercentThis) - dwElapMS); if (0 <= nEtaMs) { CTimeSpan tsEta(Generic::TimeSpanFromSeconds(nEtaMs / 1000)); sEta = tsEta.Format("%H:%M:%S"); } } } // Create new text. m_sText = sPercent; if (! sEta.IsEmpty()) { m_sText += " " + sEta; } m_dPercentLast = dPercentThis; } // Draw text. dc.SetTextColor(m_crText); dc.SetBkMode(TRANSPARENT); dc.DrawText(m_sText, &rcClient, DT_VCENTER | DT_CENTER | DT_SINGLELINE); } } // This is a mandatory call. Its only purpose is to tell this control when // processing has started. Otherwise, we would have no way to tell when to // set the start point for the ETA calculations. void MyProgressCtrl::SetEtaStart() { VALIDATE; m_dwTickStart = GetTickCount(); m_dPercentLast = 0.0; m_bSetEtaStartCalled = true; DOMYLOGD ("m_dwTickStart set to <%d>.\n", m_dwTickStart); }