/***********************************************************************/ /* 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. */ /***********************************************************************/ // Original code from msantoro@telerama.lm.com // Modified by Larry Leonard, Definitive Solutions, Inc. #include "stdafx.h" #include "MyButton.h" #include "Generic.h" #include "MyApp.h" #include "MyMemoryDC.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Defines #define TIMERID_MOUSEMOVE 1 #define TIMERID_CREATE 2 ///////////////////////////////////////////////////////////////////////////// // Statics. const int MyButton::Center(-1); const int MyButton::ColorTabSize(12); const int MyButton::FocusOffset(3); ///////////////////////////////////////////////////////////////////////////// // MyButton // Constructor. MyButton::MyButton() : m_bColorTab(false) , m_hIcon(NULL) , m_hBitmap(NULL) , m_hBitmapDisabled(NULL) , m_bDefault(false) , m_uiOldState(0U) , m_uiOldAction(0U) , m_nImageOffsetFromBorder(6) , m_nTextOffsetFromImage(8) , m_bUseOffset(true) , m_crText(NULL) , m_bMouseMoveTimerRunning(false) , m_uiCreateTimeElapsMS(0U) , m_uiAnimatedBegIconId(0U) , m_uiAnimatedEndIconId(0U) , m_uiAnimatedCurIconId(0U) , m_uiSpeedMS(0U) { } // Destructor. /* virtual */ MyButton::~MyButton() { HRESULT hr(S_OK); if (m_hIcon) { EC_B(DestroyIcon(m_hIcon)); } if (m_hBitmap) { EC_B(DeleteObject(m_hBitmap)); } if (m_hBitmapDisabled) { EC_B(DeleteObject(m_hBitmapDisabled)); } } BEGIN_MESSAGE_MAP(MyButton, CButton) //{{AFX_MSG_MAP(MyButton) ON_WM_MOUSEMOVE() ON_WM_TIMER() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyButton message handlers // Set the button's icon. bool MyButton::SetIcon(UINT uiID, int nWidth, int nHeight) { VALIDATE; _ASSERTE(0U < uiID); _ASSERTE(0 < nWidth); _ASSERTE(0 < nHeight); _ASSERTE(::IsWindow(GetSafeHwnd())); bool bReturn(false); // Make sure the style is set to "Owner Drawn". Note that on everything but // CPropertyPage's, we can just set the style ourselves - but that doesn't // work on CPropertyPages, for some reason. DWORD dwStyle(::GetWindowLong(GetSafeHwnd(), GWL_STYLE)); _ASSERTE(dwStyle & BS_OWNERDRAW && "Style for the button must be 'Owner Draw'!"); dwStyle |= BS_OWNERDRAW; // "If the previous value of the specified 32-bit integer is zero, and the // function succeeds, the return value is zero, but the function does not // clear the last error information. This makes it difficult to determine // success or failure. To deal with this, you should clear the last error // information by calling SetLastError(0) before calling SetWindowLong. // Then, function failure will be indicated by a return value of zero and // a GetLastError result that is nonzero." ::SetLastError(0); if (! (0 == ::SetWindowLong(GetSafeHwnd(), GWL_STYLE, dwStyle) && 0 != ::GetLastError())) { ; } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "SetWindowLong", ::GetLastError(), Generic::GetSysErrorString()); } // GS980730 added AfxFindResourceHandle to make MFC find resources in DLLs too... HINSTANCE hInstResource(AfxFindResourceHandle(MAKEINTRESOURCE(uiID), RT_GROUP_ICON)); if (hInstResource) { m_hIcon = reinterpret_cast (::LoadImage(hInstResource, MAKEINTRESOURCE(uiID), IMAGE_ICON, nWidth, nHeight, /* flags */ 0)); if (m_hIcon) { bReturn = true; m_nImageWidth = nWidth; m_nImageHeight = nHeight; m_hBitmap = NULL; Invalidate( /* bEraseBkgnd */ false); } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "LoadImage", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "AfxFindResourceHandle", ::GetLastError(), Generic::GetSysErrorString()); } return bReturn; } // bool MyButton::SetBitmap(UINT uiID, int nWidth, int nHeight) { VALIDATE; _ASSERTE(0U < uiID); _ASSERTE(0 < nWidth); _ASSERTE(0 < nHeight); return SetBitmapCommon(uiID, nWidth, nHeight, 0, /* bUseMask */ false); } // bool MyButton::SetMaskedBitmap(UINT uiID, int nWidth, int nHeight, COLORREF crTransparentMask) { VALIDATE; _ASSERTE(0U < uiID); _ASSERTE(0 < nWidth); _ASSERTE(0 < nHeight); return SetBitmapCommon(uiID, nWidth, nHeight, crTransparentMask, /* bUseMask */true); } // bool MyButton::HasImage() const { return m_hIcon || m_hBitmap; } // bool MyButton::SetBitmapCommon(UINT uiID, int nWidth, int nHeight, COLORREF crTransparentMask, bool bUseMask) { VALIDATE; _ASSERTE(0U < uiID); _ASSERTE(0 < nWidth); _ASSERTE(0 < nHeight); bool bReturn(false); // GS980730 added AfxFindResourceHandle to make MFC find resources in // DLLs too... HINSTANCE hInstResource(AfxFindResourceHandle(MAKEINTRESOURCE(uiID), RT_BITMAP)); if (hInstResource) { // If it is not a masked bitmap, just go through the motions as if it // was, but set then number of color mappings to 0. COLORMAP mapColor; mapColor.from = crTransparentMask; mapColor.to = ::GetSysColor(COLOR_BTNFACE); HBITMAP bmTemp(reinterpret_cast (::CreateMappedBitmap(hInstResource, uiID, IMAGE_BITMAP, &mapColor, bUseMask ? 1 : 0))); if (bmTemp) { m_hBitmap = reinterpret_cast (::CopyImage(bmTemp, IMAGE_BITMAP, nWidth, nHeight, LR_COPYDELETEORG)); if (m_hBitmap) { // Create disabled bitmap. We need to make the masked area // white so it will appear transparent when the bitmap is // rendered as an 'embossed' (disabled) image in DrawItem() // below. Since DrawState converts the image to monochrome, // white is transparent, black is graphics data. mapColor.to = RGB_WHITE; bmTemp = reinterpret_cast (::CreateMappedBitmap(hInstResource, uiID, IMAGE_BITMAP, &mapColor, bUseMask ? 1 : 0)); if (bmTemp) { m_hBitmapDisabled = reinterpret_cast (::CopyImage(bmTemp, IMAGE_BITMAP, nWidth, nHeight, LR_COPYDELETEORG)); if (m_hBitmapDisabled) { bReturn = true; m_nImageWidth = nWidth; m_nImageHeight = nHeight; m_hIcon = NULL; Invalidate( /* bEraseBkgnd */ false); } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::CopyImage", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::CreateMappedBitmap", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::CopyImage", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::CreateMappedBitmap", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "AfxFindResourceHandle", ::GetLastError(), Generic::GetSysErrorString()); } return bReturn; } // void MyButton::SetColorTab(COLORREF crTab) { VALIDATE; m_bColorTab = true; m_crColorTab = crTab; Invalidate( /* bEraseBkgnd */ false); } // bool MyButton::SetDefaultButton(bool bState) { VALIDATE; bool bPrevious(false); CDialog *pDialog = dynamic_cast (GetOwner()); ASSERT_VALID(pDialog); if (pDialog) { pDialog->SetDefID(GetDlgCtrlID()); bPrevious = m_bDefault; m_bDefault = bState; Invalidate( /* bEraseBkgnd */ false); } return bPrevious; } // int MyButton::SetImageOffset(int nPixels) { VALIDATE; int nPrevious(m_nImageOffsetFromBorder); m_bUseOffset = true; m_nImageOffsetFromBorder = nPixels; Invalidate( /* bEraseBkgnd */ false); return nPrevious; } // int MyButton::SetTextOffset(int nPixels) { VALIDATE; int nPrevious(m_nTextOffsetFromImage); m_bUseOffset = true; m_nTextOffsetFromImage = nPixels; Invalidate( /* bEraseBkgnd */ false); return nPrevious; } // CPoint MyButton::SetImagePos(CPoint pt) { VALIDATE; CPoint ptPrevious(m_ptImage); m_bUseOffset = false; m_ptImage = pt; Invalidate( /* bEraseBkgnd */ false); return ptPrevious; } // CPoint MyButton::SetTextPos(CPoint pt) { VALIDATE; CPoint ptPrevious(m_ptText); m_bUseOffset = false; m_ptText = pt; Invalidate( /* bEraseBkgnd */ false); return ptPrevious; } // void MyButton::SetTextColor(COLORREF crText) { VALIDATE; m_crText = crText; } // Centering a point helper function. void MyButton::CheckPointForCentering(CPoint &pt, int nWidth, int nHeight) { VALIDATE; _ASSERTE(0 < nWidth); _ASSERTE(0 < nHeight); CRect rcControl; GetClientRect(rcControl); if (MyButton::Center == pt.x) { pt.x = ((rcControl.Width() - nWidth) >> 1); } if (MyButton::Center == pt.y) { pt.y = ((rcControl.Height() - nHeight) >> 1); } } // void MyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { VALIDATE; ASSERT_POINTER(lpDrawItemStruct, DRAWITEMSTRUCT); // Find out what state the control and set some drawing flags according // to the state. UINT uiOffset(0U); // For position adjustment of a pushed button. UINT uiFrameStyle(0U); UINT uiNewState(lpDrawItemStruct->itemState); if (uiNewState & ODS_SELECTED) { uiFrameStyle = DFCS_PUSHED; uiOffset += 1; } int nStateFlag(0); // Normal or Disabled HBITMAP hBitmapToDraw(NULL); // Normal or Disabled bitmap (not used if uses icon) if (uiNewState & ODS_DISABLED) { nStateFlag = DSS_DISABLED; hBitmapToDraw = m_hBitmapDisabled; } else { nStateFlag = DSS_NORMAL; hBitmapToDraw = m_hBitmap; } // If this is the default button, deflate the control so everything // we do below (icon, text, focus)is adjusted properly. UINT uiNewAction(lpDrawItemStruct->itemAction); CRect rcControl(lpDrawItemStruct->rcItem); if (m_bDefault) { rcControl.DeflateRect(1, 1); } // Create the memory DC - this is needed to prevent "tearing" during // animation. CDC* pDCOriginal = CDC::FromHandle(lpDrawItemStruct->hDC); ASSERT_VALID(pDCOriginal); CFont* pFont = pDCOriginal->GetCurrentFont(); ASSERT_VALID(pFont); MyMemoryDC* pDC = new MyMemoryDC(pDCOriginal, rcControl); ASSERT_VALID(pDC); pDC->SelectObject(pFont); pDC->SetBkMode(TRANSPARENT); if (pDC) { // Draw 'default button' rectangle. if (m_bDefault) { // Can't use ODS_DEFAULT w/owner draw!! CPen *pOldPen = dynamic_cast (pDC->SelectStockObject(BLACK_PEN)); ASSERT_VALID(pOldPen); pDC->Rectangle(&lpDrawItemStruct->rcItem); // don't use deflated rectangle pDC->SelectObject(pOldPen); } // Draw button frame. Be sure the the height and width are big enough, // or DrawFrameControl() will fail. if (4 < rcControl.Height() && 4 < rcControl.Width()) { if (pDC->DrawFrameControl(&rcControl, DFC_BUTTON, DFCS_BUTTONPUSH | uiFrameStyle)) { // Draw color tab. if (m_bColorTab) { CPen penTab; if (penTab.CreatePen(PS_SOLID, 1, m_crColorTab)) { CPen* pOldPen = pDC->SelectObject(&penTab); ASSERT_VALID(pOldPen); int nXOffset(rcControl.left + 1 + uiOffset); int nYOffset(rcControl.top + 1 + uiOffset); for (UINT uiStep = 0U; uiStep < MyButton::ColorTabSize; uiStep++) { pDC->MoveTo(nXOffset, nYOffset + uiStep); pDC->LineTo(nXOffset + (MyButton::ColorTabSize - uiStep) - 1, nYOffset + uiStep); } VERIFY(pDC->SelectObject(pOldPen)); } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CPen::CreatePen", ::GetLastError(), Generic::GetSysErrorString()); } } // Get control text. CString sTitle; GetWindowText(sTitle); // Draw Image. if (HasImage()) { CPoint pt; if (m_bUseOffset) { pt.x = sTitle.IsEmpty() ? MyButton::Center : rcControl.left + m_nImageOffsetFromBorder; pt.y = MyButton::Center; } else { pt = m_ptImage; } CheckPointForCentering(pt, m_nImageWidth, m_nImageHeight); pt.Offset(uiOffset, uiOffset); if (m_hIcon) { if (pDC->DrawState(pt, CSize(m_nImageWidth, m_nImageHeight), (HICON) m_hIcon, DST_ICON | nStateFlag, static_cast (NULL))) { ; } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CDC::DrawState", ::GetLastError(), Generic::GetSysErrorString()); } } else if (m_hBitmap) { if (pDC->DrawState(pt, CSize(m_nImageWidth, m_nImageHeight), (HBITMAP) hBitmapToDraw, DST_BITMAP | nStateFlag)) { ; } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CDC::DrawState", ::GetLastError(), Generic::GetSysErrorString()); } } } // Draw Text if (! sTitle.IsEmpty()) { CPoint pt; CSize sizeText(pDC->GetTextExtent(sTitle)); if (m_bUseOffset) { CRect rcClient; GetClientRect(rcClient); pt.x = ! HasImage() ? MyButton::Center : m_nImageWidth + m_nTextOffsetFromImage + m_nImageOffsetFromBorder + (max(0, rcClient.Width() - (m_nImageWidth + m_nTextOffsetFromImage + m_nImageOffsetFromBorder) - sizeText.cx) / 2); pt.x = ! HasImage() ? MyButton::Center : m_nImageWidth + m_nTextOffsetFromImage + m_nImageOffsetFromBorder; pt.y = MyButton::Center; } else { pt = m_ptText; } // If we are centering the text vertically, it looks best // if we center based on the height of the text, then move // it up 1 more pixel. int nOffsetFixY(pt.y == MyButton::Center ? -1 : 0); CheckPointForCentering(pt, sizeText.cx, sizeText.cy); pt.Offset(uiOffset, uiOffset + nOffsetFixY); if (m_crText) { pDC->SetTextColor(m_crText); } pDC->DrawState(pt, CSize(0,0), sTitle, DST_PREFIXTEXT | nStateFlag, true, 0, (CBrush*) NULL); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CDC::DrawFrameControl", ::GetLastError(), Generic::GetSysErrorString()); } } // Draw focus rectangle. if (! (uiNewState & ODS_DISABLED)) { // Make sure it's not disabled // Redraw the focus. if (uiNewState & ODS_FOCUS) { CRect rc(rcControl); // As control gets smaller, decrease focus size. int nDeflate(min(MyButton::FocusOffset, min(rc.Width(), rc.Height()) >> 2)); rc.DeflateRect(nDeflate, nDeflate); pDC->DrawFocusRect(&rc); } } m_uiOldAction = uiNewAction; m_uiOldState = uiNewState; delete pDC; pDC = NULL; } else { _ASSERTE(! "Error"); DOMYLOGST(NULL_POINTER), "pDC"); } } // Set up for icon animation. void MyButton::SetIconAnimation(UINT uiAnimatedBegIconId, UINT uiAnimatedEndIconId, UINT uiSpeedMS) { VALIDATE; _ASSERTE(uiAnimatedBegIconId); _ASSERTE(uiAnimatedEndIconId); _ASSERTE(uiSpeedMS); m_uiAnimatedBegIconId = uiAnimatedBegIconId; m_uiAnimatedEndIconId = uiAnimatedEndIconId; m_uiAnimatedCurIconId = m_uiAnimatedBegIconId; m_uiSpeedMS = uiSpeedMS; // Animate the icon for a second when the button s first displayed. VERIFY(TIMERID_CREATE == SetTimer(TIMERID_CREATE, m_uiSpeedMS, NULL)); } // WM_MOUSEMOVE void MyButton::OnMouseMove(UINT nFlags, CPoint point) { VALIDATE; // If icon animation has been enabled. if (m_uiAnimatedBegIconId && m_uiAnimatedEndIconId) { // Start the time is not currently running. if (! m_bMouseMoveTimerRunning) { VERIFY(TIMERID_MOUSEMOVE == SetTimer(TIMERID_MOUSEMOVE, m_uiSpeedMS, NULL)); } } CButton::OnMouseMove(nFlags, point); } // WM_TIMER void MyButton::OnTimer(UINT nIDEvent) { VALIDATE; if (TIMERID_MOUSEMOVE == nIDEvent || TIMERID_CREATE == nIDEvent) { _ASSERTE(m_uiAnimatedBegIconId); _ASSERTE(m_uiAnimatedEndIconId); _ASSERTE(m_uiAnimatedCurIconId); // If the cursor is over the button, change the icon. bool bCursorInButton(false); if (TIMERID_MOUSEMOVE == nIDEvent) { CPoint ptCursorScreen; VERIFY(::GetCursorPos(&ptCursorScreen)); CRect rcButtonScreen; GetWindowRect(&rcButtonScreen); bCursorInButton = rcButtonScreen.PtInRect(ptCursorScreen); } if (bCursorInButton || TIMERID_CREATE == nIDEvent) { SetIcon(m_uiAnimatedCurIconId, m_nImageWidth, m_nImageHeight); // Set the icon id for next time. ++m_uiAnimatedCurIconId; if (m_uiAnimatedCurIconId > m_uiAnimatedEndIconId) { m_uiAnimatedCurIconId = m_uiAnimatedBegIconId; } // Is it time to kill the Create timer? if (TIMERID_CREATE == nIDEvent) { m_uiCreateTimeElapsMS += m_uiSpeedMS; if (1000 < m_uiCreateTimeElapsMS) { VERIFY(KillTimer(TIMERID_CREATE)); } } } else { // Else, kill the timer. VERIFY(KillTimer(TIMERID_MOUSEMOVE)); m_bMouseMoveTimerRunning = false; } } CButton::OnTimer(nIDEvent); }