// ============================================================================= // Class Implementation : COXSplashWnd // ============================================================================= // // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "OXSplashWnd.h" #include #include "UTB64Bit.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif BEGIN_MESSAGE_MAP(COXSplashWnd, CWnd) //{{AFX_MSG_MAP(COXSplashWnd) ON_WM_TIMER() ON_WM_CREATE() ON_WM_PAINT() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // class COXSplashWnd int COXSplashWnd::m_nDefaultTimeout = 750; COXSplashWnd* COXSplashWnd::c_pGlobalWnd = NULL; // --- A pointer to the one global splash window object (may be NULL) BOOL COXSplashWnd::c_bShowSplashWnd = TRUE; // --- Whether the global splash window is enabled // Data members ------------------------------------------------------------- // protected: // int m_nTimeout; // --- The time out value used to let the splash window disappear automatically // BOOL m_bAutoDelete; // --- Whether this object should be deleted when the window is destroyed // COXSplashWndDIB m_dib; // --- The DIB used for this splash window // CRgn m_rgn; // --- The region of this splash window // COLORREF m_crBorder; // --- Color of the border // BYTE m_crBorderR; // --- Red value of the border color (if valid color) // BYTE m_crBorderG; // --- Green value of the border color (if valid color) // BYTE m_crBorderB; // --- Blue value of the border color (if valid color) // BYTE m_nTolerance; // --- Tolerance used to decide whether a color belongs to the border or not // Member functions --------------------------------------------------------- COXSplashWnd::COXSplashWnd() { m_bAutoDelete = FALSE; m_bUserCancel = TRUE; } COXSplashWnd::~COXSplashWnd() { if (c_pGlobalWnd == this) c_pGlobalWnd = NULL; } BOOL COXSplashWnd::LoadGlobalBitmap(LPCTSTR lpszResourceName, COLORREF crBorder/* = CLR_DEFAULT */, LPPOINT pStartPoint /* = NULL */, BYTE nTolerance /* = 0 */) { if(c_pGlobalWnd==NULL) { c_pGlobalWnd = new COXSplashWnd; c_pGlobalWnd->SetAutoDelete(); } return c_pGlobalWnd->LoadBitmap(lpszResourceName, crBorder, pStartPoint, nTolerance); } BOOL COXSplashWnd::LoadGlobalBitmapFile(LPCTSTR lpszFileName, COLORREF crBorder/* = CLR_DEFAULT */, LPPOINT pStartPoint /* = NULL */, BYTE nTolerance /* = 0 */) { if(c_pGlobalWnd==NULL) { c_pGlobalWnd = new COXSplashWnd; c_pGlobalWnd->SetAutoDelete(); } return c_pGlobalWnd->LoadBitmapFile(lpszFileName, crBorder, pStartPoint, nTolerance); } BOOL COXSplashWnd::LoadBitmap(LPCTSTR lpszResourceName, COLORREF crBorder /* = CLR_DEFAULT */, LPPOINT pStartPoint /* = NULL */, BYTE nTolerance /* = 0 */) { if(!m_dib.LoadResource(lpszResourceName, TRUE)) return FALSE; // (2) determine bitmap's shaping region m_crBorder = crBorder; m_nTolerance = nTolerance; if(m_crBorder==CLR_NONE) { m_rgn.DeleteObject(); return TRUE; } CPoint ptStart(0,0); if (pStartPoint != NULL) ptStart = *pStartPoint; if (m_crBorder == CLR_DEFAULT) { m_crBorder = m_dib.GetPixel(ptStart); if (m_crBorder == CLR_INVALID) { TRACE0("COXSplashWnd::LoadBitmap(): invalid starting point, (0,0) will be used instead.\r\n"); m_crBorder = m_dib.GetPixel(0,0); } } if (m_crBorder != CLR_INVALID && m_crBorder != CLR_NONE) { m_crBorderR = GetRValue(m_crBorder); m_crBorderG = GetGValue(m_crBorder); m_crBorderB = GetBValue(m_crBorder); if (BuildRegion(ptStart)) return TRUE; } m_rgn.DeleteObject(); return FALSE; } BOOL COXSplashWnd::LoadBitmapFile(LPCTSTR lpszFileName, COLORREF crBorder /* = CLR_DEFAULT */, LPPOINT pStartPoint /* = NULL */, BYTE nTolerance /* = 0 */) { if(!m_dib.LoadFile(lpszFileName,TRUE)) return FALSE; // (2) determine bitmap's shaping region m_crBorder = crBorder; m_nTolerance = nTolerance; if(m_crBorder==CLR_NONE) { m_rgn.DeleteObject(); return TRUE; } CPoint ptStart(0,0); if (pStartPoint != NULL) ptStart = *pStartPoint; if (m_crBorder == CLR_DEFAULT) { m_crBorder = m_dib.GetPixel(ptStart); if (m_crBorder == CLR_INVALID) { TRACE0("COXSplashWnd::LoadBitmap(): invalid starting point, (0,0) will be used instead.\r\n"); m_crBorder = m_dib.GetPixel(0,0); } } if(m_crBorder != CLR_INVALID) { m_crBorderR = GetRValue(m_crBorder); m_crBorderG = GetGValue(m_crBorder); m_crBorderB = GetBValue(m_crBorder); if (BuildRegion(ptStart)) return TRUE; } m_rgn.DeleteObject(); return FALSE; } BOOL COXSplashWnd::Show(int nTimeOut /* = m_nDefaultTimeout */, CWnd* pParentWnd /* = NULL */, CPoint* pptDisplayAt /* = NULL */) { if ((c_pGlobalWnd == this) && (!c_bShowSplashWnd)) { TRACE0("COXSplashWnd::Show(): The global splash window was disabled.\r\n"); return FALSE; } if (m_dib.GetBitmap() == NULL) { TRACE0("COXSplashWnd::Show(): Failed: no valid bitmap was loaded."); return FALSE; } m_nTimeout = nTimeOut; if (m_nTimeout <= 0 && m_nTimeout != SPLASH_NOTIMER) { TRACE1("COXSplashWnd::Show(): Invalid timeout value has been corrected to %d.\r\n", m_nDefaultTimeout); m_nTimeout = m_nDefaultTimeout; } // destroy it if already created if (m_hWnd != NULL && ::IsWindow(m_hWnd)) OnTimer(OXSPLASHWND_TIMERID_HIDE); CWnd* pTempParent = pParentWnd; DWORD dwExtraStyle(0); if (pTempParent == NULL) { if(!m_invisParent.CreateEx(WS_EX_TOPMOST, AfxRegisterWndClass(CS_CLASSDC), _T(""), WS_POPUP, 0, 0, 1, 1, NULL, NULL)) { TRACE0("COXSplashWnd::Show(): fail to create invisible parent window. Using WS_EX_TOOLWINDOW\r\n"); dwExtraStyle = WS_EX_TOOLWINDOW; } pTempParent = &m_invisParent; } BITMAP bm; m_dib.GetBitmapInfo(bm); if (!CreateEx(WS_EX_TOPMOST | dwExtraStyle, AfxRegisterWndClass(CS_CLASSDC, AfxGetApp()->LoadStandardCursor(IDC_ARROW)), _T(""), WS_POPUP | WS_VISIBLE, 0, 0, bm.bmWidth, bm.bmHeight, (pTempParent != NULL ? pTempParent->GetSafeHwnd() : NULL), NULL)) { TRACE0("COXSplashWnd::Show(): fail to create splash window.\r\n"); if (m_bAutoDelete) delete this; if(pTempParent==&m_invisParent) m_invisParent.DestroyWindow(); return FALSE; } SetFocus(); if(pptDisplayAt==NULL) UpdateWindow(); else MoveWindow(pptDisplayAt->x,pptDisplayAt->y,bm.bmWidth,bm.bmHeight); return TRUE; } BOOL COXSplashWnd::Hide(int nTimeOut /* = 0 */) { if (nTimeOut <= 0) { // destroy splash if (m_hWnd != NULL && ::IsWindow(m_hWnd)) OnTimer(OXSPLASHWND_TIMERID_HIDE); m_nTimeout = 0; return TRUE; } m_nTimeout = nTimeOut; return (BOOL)SetTimer(OXSPLASHWND_TIMERID_HIDE, m_nTimeout, NULL); } BOOL COXSplashWnd::SetRegion (CRgn& rgn) { m_rgn.DeleteObject(); m_rgn.CreateRectRgn(0,0,1,1); int nResult = m_rgn.CopyRgn(&rgn); #ifdef _DEBUG if (nResult == NULLREGION) TRACE0("COXSplashWnd::SetRegion(): an empty region was set.\r\n"); #endif if (nResult == ERROR) { TRACE0("COXSplashWnd::SetRegion(): failed to set region.\r\n"); m_rgn.DeleteObject(); return FALSE; } return TRUE; } // protected: BOOL COXSplashWnd::BuildRegion(POINT ptStart) // --- In : ptStart, the starting point to search for region boundary // --- Out : // --- Returns : TRUE if successful; FALSE otherwise // --- Effect : calculate the region to be visible in a bitmap { CArray vertexs; CPoint ptMax(0,0); CPoint pt0(0,0); CPoint pt1(0,0); CPoint pt(0,0); CPoint ptPrev(0,0); CSize sizeInc(0,0); int i = 0; int iFirst = 0; int iLast = 0; int iPrev = 0; // move directions: index (xInc, yInc) alternate_index // 0 (-1,-1) 8 1 ( 0,-1) 9 2 ( 1,-1) 10 // 7 (-1, 0) 15 3 ( 1, 0) 11 // 6 (-1, 1) 14 5 ( 0, 1) 13 4 ( 1, 1) 12 const int xd[] = {-1,0,1,1,1,0,-1,-1,-1,0,1,1,1,0,-1,-1}; const int yd[] = {-1,-1,-1,0,1,1,1,0,-1,-1,-1,0,1,1,1,0}; const int nextdir[] = {6,0,0,2,2,4,4,6,6,0,0,2,2,4,4,6}; BITMAP bm; m_dib.GetBitmapInfo(bm); ptMax = CPoint(bm.bmWidth - 1, bm.bmHeight - 1); // find a start point that's outside the region UNREFERENCED_PARAMETER(ptStart); pt0 = CPoint(0,0); if (!IsBorder(pt0)) pt0 = CPoint(-1,-1); // search diagonally for first point that's within the region sizeInc.cx = (pt0.x > (ptMax.x / 2)) ? -1 : 1; sizeInc.cy = (pt0.y > (ptMax.y / 2)) ? -1 : 1; for (pt1 = pt0 + sizeInc; IsBorder(pt1, FALSE); pt1 += sizeInc) ; pt0 = pt1 - sizeInc; // if not found after hitting the boundary, search by scan lines if (m_dib.GetPixel(pt1) == CLR_INVALID) { for (pt1.y = 0; pt1.y <= ptMax.y; pt1.y++) { for (pt1.x = 0; pt1.x <= ptMax.x && IsBorder(pt1); pt1.x++) ; if (pt1.x <= ptMax.x) break; } if (ptMax.y < pt1.y) return FALSE; pt0 = pt1 - CSize(0, 1); } // now pt1 should be the starting point that's within the region // and pt0 should be a neigboring point that's outside the region ASSERT(IsBorder(pt0) && !IsBorder(pt1)); // clockwise find border/region boundary for (i = 0; i <= 7; i++) { pt = pt1 + CSize(xd[i], yd[i]); if (!IsBorder(pt)) break; } if (i == 8) return FALSE; // important: assign second point as the start point to prevent // diagonal circumvent pt1 = pt; vertexs.Add(pt); // cycle until the most beginning point is found back again do { iPrev = i; ptPrev = pt; iFirst = nextdir[i]; iLast = iFirst + 6; for (i = iFirst; i <= iLast; i++) { pt = ptPrev + CSize(xd[i], yd[i]); if (!IsBorder(pt)) break; } if (i == iPrev) { // moving forward on the same direction // replace the last point with this new one, so that region // definition could be simpler vertexs[vertexs.GetSize()-1] = pt; } else { // direction changed, has to add a new vertex vertexs.Add(pt); } if(vertexs.GetSize()%3000==0) TRACE(_T("Add point: %d,%d, number %d\n"),pt.x,pt.y,vertexs.GetSize()); } while (pt != pt1); m_rgn.DeleteObject(); if (!m_rgn.CreatePolygonRgn(vertexs.GetData(), PtrToInt(vertexs.GetSize()), ALTERNATE)) { ASSERT((HRGN)m_rgn == NULL); return FALSE; } return TRUE; } BOOL COXSplashWnd::IsBorder(POINT pt, BOOL bConsiderInvalidAsBorder /* = TRUE */) // --- In : pt, the point to examine // --- Out : // --- Returns : TRUE if point is in border; FALSE if point is in visible region // --- Effect : determine whether a point should be part of the border { COLORREF cr; cr = m_dib.GetPixel(pt); if (cr == CLR_INVALID) return bConsiderInvalidAsBorder; if (m_nTolerance < abs(GetRValue(cr) - m_crBorderR)) return FALSE; if (m_nTolerance < abs(GetGValue(cr) - m_crBorderG)) return FALSE; if (m_nTolerance < abs(GetBValue(cr) - m_crBorderB)) return FALSE; return TRUE; } void COXSplashWnd::PostNcDestroy() { if (m_bAutoDelete) delete this; else CWnd::PostNcDestroy(); } void COXSplashWnd::OnTimer(UINT nIDEvent) { if (nIDEvent == OXSPLASHWND_TIMERID_HIDE) { ShowWindow(SW_HIDE); KillTimer(nIDEvent); ReleaseCapture(); // If we made our own invisible parent, let's destroy it and // it will take care of destroying us if (::IsWindow(m_invisParent.m_hWnd)) // m_invisParent.DestroyWindow(); ::DestroyWindow(m_invisParent.GetSafeHwnd()); else // We didn't provide our own parent, so we must destroy ourselves // DestroyWindow(); ::DestroyWindow(GetSafeHwnd()); } } int COXSplashWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1; if ((HRGN)m_rgn != NULL) { CRgn rgn; rgn.CreateRectRgn(0,0,1,1); switch(rgn.CopyRgn(&m_rgn)) { case ERROR: TRACE0("COXSplashWnd::OnCreate(): fail to load the calculated region.\r\n"); rgn.DeleteObject(); break; case NULLREGION: TRACE0("COXSplashWnd::OnCreate(): empty region was encountered.\r\n"); rgn.DeleteObject(); break; default: ::SetWindowRgn(m_hWnd, (HRGN)rgn.Detach(), TRUE); } } CenterWindow(); if (m_nTimeout != SPLASH_NOTIMER) SetTimer(OXSPLASHWND_TIMERID_HIDE, m_nTimeout, NULL); SetCapture(); ::SetCursor(::LoadCursor(NULL, IDC_ARROW)); return 0; } void COXSplashWnd::OnPaint() { CPaintDC dc(this); m_dib.Draw(&dc); } BOOL COXSplashWnd::PreTranslateMessage(MSG* pMsg) { if(m_bUserCancel) { if((pMsg->message>=WM_MOUSEFIRST && pMsg->message<=WM_MOUSELAST && pMsg->message!=WM_MOUSEMOVE) || (pMsg->message>=WM_KEYFIRST && pMsg->message<=WM_KEYLAST)) { OnTimer(OXSPLASHWND_TIMERID_HIDE); return TRUE; } } return CWnd::PreTranslateMessage(pMsg); } // end of OXSplashWnd.cpp