// XTPTipWindow.cpp : implementation of the CXTPTipWindow class. // // This file is a part of the XTREME CONTROLS MFC class library. // (c)1998-2012 Codejock Software, All Rights Reserved. // // THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE // RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN // CONSENT OF CODEJOCK SOFTWARE. // // THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED // IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO // YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A // SINGLE COMPUTER. // // CONTACT INFORMATION: // support@codejock.com // http://www.codejock.com // ///////////////////////////////////////////////////////////////////////////// // Thanks to Michael Berlenz for helping to extend the CXTPTipWindow class. #include "stdafx.h" #include "Common/XTPDrawHelpers.h" #include "Common/XTPColorManager.h" #include "Common/XTPSystemHelpers.h" #include "../Util/XTPGlobal.h" #include "XTPTipWindow.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define CS_DROPSHADOW 0x00020000 ///////////////////////////////////////////////////////////////////////////// // CXTPTipWindow ///////////////////////////////////////////////////////////////////////////// CXTPTipWindow::CXTPTipWindow() { m_sizeMargin = CSize(0, 0); m_nLineSpace = 0; m_dwTipStyle = 0; m_nElapseTimerEventID = 1; m_pParentWnd = NULL; m_strTitle = _T(""); m_strDescrip = _T(""); m_crBackColor = ::GetSysColor(COLOR_INFOBK); m_crTextColor = ::GetSysColor(COLOR_INFOTEXT); m_sizeTitle = CSize(0, 0); // m_bMouseWasClickedSoDoNotShowTip = FALSE; m_ptMousePos = CPoint(0, 0); m_bCenterHorz = FALSE; m_nDelayTimerEventID = 2; m_bDelayTimerRunning = FALSE; m_pointTipOffsetPos = CPoint(0, 20); //20 = mouse cursor height m_pnTabStopPositions = NULL; m_nTabPositions = 0; m_bSystemShadow = FALSE; m_rectTipArea.SetRectEmpty(); } CXTPTipWindow::~CXTPTipWindow() { } BOOL CXTPTipWindow::Create(CWnd* pParentWnd) { // register the wnd class. XTPDrawHelpers()->RegisterWndClass(0, _T("XTPTipWindow"), CS_SAVEBITS | CS_HREDRAW | CS_VREDRAW); // call the base class for creation. if (!CWnd::CreateEx(0, _T("XTPTipWindow"), _T(""), WS_POPUP, 0, 0, 0, 0, pParentWnd->m_hWnd, 0, NULL)) { TRACE0("Failed to create popup window.\n"); return FALSE; } m_pParentWnd = pParentWnd; return TRUE; } BEGIN_MESSAGE_MAP(CXTPTipWindow, CWnd) //{{AFX_MSG_MAP(CXTPTipWindow) ON_WM_MOUSEMOVE() ON_WM_KILLFOCUS() ON_WM_PAINT() ON_WM_TIMER() ON_WM_ERASEBKGND() ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() ON_WM_MBUTTONDOWN() //}}AFX_MSG_MAP END_MESSAGE_MAP() void CXTPTipWindow::CreateShadow() { if (((m_dwTipStyle & (TWS_XTP_ALPHASHADOW | TWS_XTP_DROPSHADOW)) == (TWS_XTP_ALPHASHADOW | TWS_XTP_DROPSHADOW)) && XTPSystemVersion()->IsWinXPOrGreater()) // Windows XP only { SetClassLong(m_hWnd, GCL_STYLE, GetClassLong(m_hWnd, GCL_STYLE) | CS_DROPSHADOW); m_bSystemShadow = TRUE; } else { SetClassLong(m_hWnd, GCL_STYLE, GetClassLong(m_hWnd, GCL_STYLE) & ~CS_DROPSHADOW); m_bSystemShadow = FALSE; } } BOOL CXTPTipWindow::ShowTipWindow(const CPoint& point, DWORD dwTipStyle/*= TWS_XTP_DROPSHADOW*/, UINT nDelay/*= 0*/, UINT nElapse/*= 5000*/, BOOL bCenterHorz /*= FALSE*/) { //Check for valid tip text if (m_strTitle.IsEmpty() && m_strDescrip.IsEmpty()) return FALSE; // Save values for InitializeSize() function m_ptMousePos = point; m_bCenterHorz = bCenterHorz; // Return if process is running if (m_bDelayTimerRunning || IsWindowVisible()) return FALSE; // Analyze tip text m_arStrings.RemoveAll(); if (m_strDescrip.Find(_T("\n")) != -1) { CString strDesc(m_strDescrip); while (strDesc.Find(_T("\n")) != -1) { int nDelim = strDesc.Find(_T("\n")); CString strAdd = strDesc.Left(nDelim); m_arStrings.Add(strAdd); strDesc = strDesc.Mid(nDelim + 1); } m_arStrings.Add(strDesc); } // Save the tip style m_dwTipStyle = dwTipStyle; CreateShadow(); if (!nDelay) { // Show the tip window InitializeSize(m_ptMousePos, m_bCenterHorz); SetWindowPos(&wndTop, m_rcScreenWindow.left, m_rcScreenWindow.top, m_rcShadow.right - m_rcWindow.left, m_rcShadow.bottom - m_rcWindow.top, SWP_SHOWWINDOW | SWP_NOACTIVATE); //see also InitializeSize() SetCapture(); } // If a time-out value was specified, start the timer. if (nElapse || nDelay) { if (nDelay) { SetTimer(m_nDelayTimerEventID, nDelay, NULL); //if timer runns out, window will be shown m_bDelayTimerRunning = TRUE; } if (nElapse) SetTimer(m_nElapseTimerEventID, nDelay + nElapse, NULL); //if timer runns out, window will be hide } return TRUE; // success! } ////////////////////////////////////////////////////////////////////////// // ShowTipWindow // ---------------------------------------------------------------------------- // rectCtrl = if the mouse is inside this rectangle the tip will be shown // => client coordinates of the window that owns this tip window // pointMousePos => client coordinates of the window that owns this tip window // BOOL CXTPTipWindow::ShowTipWindow(const CRect& rectCtrl, const CPoint& pointMousePos, DWORD dwTipStyle/*= TWS_XTP_DROPSHADOW*/, UINT nDelay/*= 0*/, UINT nElapse/*= 5000*/, BOOL bCenterHorz /*= FALSE*/, BOOL bShowTipAtCursorPos/*= FALSE*/) { //Check for valid tip text // if (m_strTitle.IsEmpty() && m_strDescrip.IsEmpty()) //Do not process this check here! // return FALSE; //Check for zero rectangle if (rectCtrl.IsRectNull()) return ShowTipWindow(pointMousePos, dwTipStyle, nDelay, nElapse, bCenterHorz); //Check mouse was clicked -> do not show tip window // if (m_bMouseWasClickedSoDoNotShowTip && rectCtrl == m_rectLast) // return FALSE; //In case the tip window is visible hide it HideTipWindow(); //Calculate offset for bShowTipAtCursorPos = TRUE CPoint pointMove = pointMousePos - rectCtrl.TopLeft(); pointMove.Offset(m_pointTipOffsetPos); //Copy rectCtrl and move rectangle to zero origin m_rectTipArea = rectCtrl; //Offset rectangle CPoint ptTipWindowPos = rectCtrl.TopLeft(); if (bShowTipAtCursorPos) //else show window at top left corner of rectCtrl { ptTipWindowPos += pointMove; } // m_bMouseWasClickedSoDoNotShowTip = FALSE; //m_rectLast = rectCtrl; m_pParentWnd->ClientToScreen(&ptTipWindowPos); m_pParentWnd->ClientToScreen(&m_rectTipArea); return ShowTipWindow(ptTipWindowPos, dwTipStyle, nDelay, nElapse, bCenterHorz); } void CXTPTipWindow::HideTipWindow() { // Kill the timers KillTimer(m_nElapseTimerEventID); KillTimer(m_nDelayTimerEventID); m_bDelayTimerRunning = FALSE; // Release capture if (GetCapture() == this) ReleaseCapture(); // Hide the tip window if (IsWindowVisible()) ShowWindow(SW_HIDE); //Initialize variables m_rectTipArea.SetRectEmpty(); } void CXTPTipWindow::InitializeSize(const CPoint& point, BOOL bCenterHorz /*FALSE*/) { // Create a temporary window dc, we are not actually // going to paint anything, we just want to get the sizes // of the title and description text. CWindowDC dc(NULL); // Get the size of the title string. CFont* pOldFont = dc.SelectObject(&XTPAuxData().fontBold); m_sizeTitle = dc.GetTabbedTextExtent(m_strTitle, m_nTabPositions, m_pnTabStopPositions); // Get the size of the description string. dc.SelectObject(&XTPAuxData().font); CSize sizeDescrip(0, 0); // description text size. if (m_arStrings.GetSize() == 0) { sizeDescrip = dc.GetTabbedTextExtent(m_strDescrip, m_nTabPositions, m_pnTabStopPositions); } else { TEXTMETRIC tm; // get the text metrics for the device context that is using the dc.GetTextMetrics(&tm); // control's font, this will give us the text height in pixels. CSize sizeTemp; int nStrings; for (nStrings = 0; nStrings < m_arStrings.GetSize(); ++nStrings) { // Strings could be empty because only a line break could be in the line. // A empty string will return zero text extend. But we still have to count this line! if (m_arStrings[nStrings].IsEmpty()) sizeDescrip.cy += tm.tmHeight; else { sizeTemp = dc.GetTabbedTextExtent(m_arStrings[nStrings], m_nTabPositions, m_pnTabStopPositions); sizeDescrip.cy += sizeTemp.cy; sizeDescrip.cx = __max(sizeDescrip.cx, sizeTemp.cx); } } } // Restore GDI object. dc.SelectObject(pOldFont); // Define the width of the tip window. int nWidth = __max(m_sizeTitle.cx, sizeDescrip.cx); nWidth = (m_sizeMargin.cx * 2 + nWidth); // Define the height of the tip window. int nHeight = (m_sizeMargin.cy * 2 + ((m_strTitle.GetLength() > 0) ? m_sizeTitle.cy + m_nLineSpace : 0) + sizeDescrip.cy); // Set the sizes for the tip window and its shadow. m_rcScreenWindow.left = point.x; m_rcScreenWindow.top = point.y; m_rcScreenWindow.right = point.x + nWidth + 2; //+2 for better view m_rcScreenWindow.bottom = point.y + nHeight; if (bCenterHorz) m_rcScreenWindow.OffsetRect(-m_rcScreenWindow.Width()/2, 0); // ensure that the tip window is visible. CRect rcWork = XTPMultiMonitor()->GetWorkArea(point); CSize size = m_rcScreenWindow.Size(); // move right if (m_rcScreenWindow.left < rcWork.left) { m_rcScreenWindow.left = rcWork.left; m_rcScreenWindow.right = m_rcScreenWindow.left + size.cx; } // move left else if (m_rcScreenWindow.right > rcWork.right) { m_rcScreenWindow.right = rcWork.right; m_rcScreenWindow.left = m_rcScreenWindow.right - size.cx; } // move up if (m_rcScreenWindow.bottom > rcWork.bottom) { m_rcScreenWindow.bottom = rcWork.bottom; m_rcScreenWindow.top = m_rcScreenWindow.bottom - size.cy; } // Initialize the size of the shadow rect. m_rcWindow = m_rcScreenWindow; m_rcWindow.top = 0; m_rcWindow.left = 0; m_rcWindow.right = m_rcScreenWindow.Width(); m_rcWindow.bottom = m_rcScreenWindow.Height(); m_rcShadow = m_rcWindow; if ((m_dwTipStyle & TWS_XTP_DROPSHADOW) && !m_bSystemShadow) m_rcShadow.OffsetRect(5, 5); } void CXTPTipWindow::OnKillFocus(CWnd* pNewWnd) { CWnd::OnKillFocus(pNewWnd); HideTipWindow(); // Repaint the area where the shadow was displayed. if ((m_dwTipStyle & TWS_XTP_DROPSHADOW) && !m_bSystemShadow) { m_pParentWnd->RedrawWindow(0, 0, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE | RDW_ALLCHILDREN); } } int CXTPTipWindow::CheckValue(int iValue) { return ((iValue > 255) ? 255 : ((iValue < 0) ? 0 : iValue)); } COLORREF CXTPTipWindow::AlphaPixel(COLORREF crPixel, int i) { return RGB( CheckValue(GetRValue(crPixel)-i), CheckValue(GetGValue(crPixel)-i), CheckValue(GetBValue(crPixel)-i)); } void CXTPTipWindow::DrawShadowRect(CDC* pDC, const CRect& rcShadow) { if ((m_dwTipStyle & TWS_XTP_ALPHASHADOW) == 0) { // Bit pattern for a monochrome brush with every // other pixel turned off WORD bits[] = { 0x0055, 0x00AA, 0x0055, 0x00AA, 0x0055, 0x00AA, 0x0055, 0x00AA }; // Need a monochrome pattern bitmap CBitmap bitmap; bitmap.CreateBitmap(8, 8, 1, 1, &bits); // Create the pattern brush CBrush brush; brush.CreatePatternBrush(&bitmap); CBrush *pOldBrush = pDC->SelectObject(&brush); // Turn every other pixel to black COLORREF clrBk = pDC->SetBkColor(RGB(0x00, 0x00, 0x00)); COLORREF clrText = pDC->SetTextColor(RGB(0xFF, 0xFF, 0xFF)); // 0x00A000C9 is the ROP code to AND the brush with the destination pDC->PatBlt(rcShadow.left, rcShadow.top, rcShadow.Width(), rcShadow.Height(), (DWORD)0x00A000C9); //DPa - raster code // Restore the device context pDC->SelectObject(pOldBrush); pDC->SetTextColor(clrText); pDC->SetBkColor(clrBk); brush.DeleteObject(); bitmap.DeleteObject(); } else { CRect rc1 = rcShadow; CRect rc2 = rcShadow; CRect rc3 = rcShadow; CRect rc4 = rcShadow; CRect rc5 = rcShadow; CRect rc6 = rcShadow; rc2.DeflateRect(1, 1); rc3.DeflateRect(2, 2); rc4.DeflateRect(3, 3); rc5.DeflateRect(4, 4); rc6.DeflateRect(5, 5); int cx = rcShadow.Width() + 5; int cy = rcShadow.Height() + 5; int x; for (x = 5; x < cx; ++x) { int y; for (y = 5; y < cy; ++y) { CPoint pt(x, y); int iAlpha = 0; // area covered by menu...continue. if (rc6.PtInRect(pt)) { continue; } else if (rc5.PtInRect(pt)) { iAlpha = 75; } else if (rc4.PtInRect(pt)) { iAlpha = 60; } else if (rc3.PtInRect(pt)) { iAlpha = 45; } else if (rc2.PtInRect(pt)) { iAlpha = 30; } else if (rc1.PtInRect(pt)) { iAlpha = 15; } if (iAlpha != 0) { COLORREF crAlpha = AlphaPixel(pDC->GetPixel(x, y), iAlpha); pDC->SetPixel(x, y, crAlpha); } } } } } void CXTPTipWindow::OnPaint() { CPaintDC dc(this); // device context for painting // Set the background mode to transparent. dc.SetBkMode(TRANSPARENT); // Draw a shadow on the the parent window, we will need the // parent's device context to do this. if ((m_dwTipStyle & TWS_XTP_DROPSHADOW) && !m_bSystemShadow) DrawShadowRect(&dc, m_rcShadow); // use the system tooltip colors for text and background. dc.SetTextColor(m_crTextColor); dc.FillSolidRect(m_rcWindow, m_crBackColor); // Draw a border around the window. dc.Draw3dRect(m_rcWindow, GetXtremeColor(COLOR_3DDKSHADOW), GetXtremeColor(COLOR_3DDKSHADOW)); // Draw an inner light grey border. if (m_dwTipStyle & TWS_XTP_THICKBORDER) { CRect rect(m_rcWindow); rect.DeflateRect(1, 1); dc.Draw3dRect(rect, GetXtremeColor(COLOR_3DSHADOW), GetXtremeColor(COLOR_3DSHADOW)); } // Draw the title text. CFont* pOldFont = dc.SelectObject(&XTPAuxData().fontBold); dc.TabbedTextOut(m_sizeMargin.cx + 1, m_sizeMargin.cy, m_strTitle, m_nTabPositions, m_pnTabStopPositions, m_sizeMargin.cx + 1); // Draw the description text. dc.SelectObject(&XTPAuxData().font); if (m_arStrings.GetSize() == 0) dc.TabbedTextOut(m_sizeMargin.cx + 1, m_sizeMargin.cy + ((m_strTitle.GetLength() > 0) ? m_sizeTitle.cy + m_nLineSpace : 0), m_strDescrip, m_nTabPositions, m_pnTabStopPositions, m_sizeMargin.cx + 1); else { // Get the string height for stepping through CSize sizeDesc = dc.GetTabbedTextExtent(m_arStrings[0], m_nTabPositions, m_pnTabStopPositions); // The second parameter is long because we add the height of the strings already done, and also // we don't add anything to get below the title if the length of the title is 0. int nStrings; for (nStrings = 0; nStrings < m_arStrings.GetSize(); ++nStrings) dc.TabbedTextOut(m_sizeMargin.cx + 1, m_sizeMargin.cy + (nStrings * sizeDesc.cy) + ((m_strTitle.GetLength() > 0) ? m_sizeTitle.cy + m_nLineSpace : 0), m_arStrings[nStrings], m_nTabPositions, m_pnTabStopPositions, m_sizeMargin.cx + 1); } // Restore the device context. dc.SelectObject(pOldFont); } void CXTPTipWindow::OnTimer(UINT_PTR nIDEvent) { if (m_nElapseTimerEventID == nIDEvent) { // Hide the tip window. HideTipWindow(); } if (m_nDelayTimerEventID == nIDEvent) { KillTimer(m_nDelayTimerEventID); if (!m_rectTipArea.IsRectNull()) { //If someone moves the mouse over a tip area in the client window //it may happen that the tip delay timer will be started. During this //delay time the user may move the mouse out of the application window. //After the timer runs out the tip window will be shown but because //of the fast mouse move the cursor will not be located inside the tip area. //Because the cursor my located outside the application we will not get //the mouse move messages at CXTPTipWindow::OnMouseMove(). //So better check if the mouse is still located inside the tip area. //If not do not show the tip window. CPoint pointMouse; GetCursorPos(&pointMouse); if (!m_rectTipArea.PtInRect(pointMouse)) return; } // Show the tip window InitializeSize(m_ptMousePos, m_bCenterHorz); //Calculates m_rcScreenWindow values SetWindowPos(&wndTop, m_rcScreenWindow.left, m_rcScreenWindow.top, m_rcScreenWindow.Width(), m_rcScreenWindow.Height(), SWP_SHOWWINDOW | SWP_NOACTIVATE); //+5 because of the shadow SetCapture(); m_bDelayTimerRunning = FALSE; } CWnd::OnTimer(nIDEvent); } BOOL CXTPTipWindow::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; } ////////////////////////////////////////////////////////////////////////// // // Hide the tip window and forward the message to the window at mouse point. // void CXTPTipWindow::ForwardMessage(UINT uiMsg, UINT nFlags, CPoint point) { // m_bMouseWasClickedSoDoNotShowTip = TRUE; HideTipWindow(); CPoint pt(point); ClientToScreen(&pt); CWnd* pClick = WindowFromPoint(pt); if (!pClick->IsChild(this) && pClick != this) { LRESULT iHitTest = pClick->SendMessage(WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y)); if (AfxGetMainWnd() != NULL) pClick->SendMessage(WM_MOUSEACTIVATE, (LPARAM)AfxGetMainWnd()->m_hWnd, MAKELPARAM(iHitTest , uiMsg)); pClick->ScreenToClient(&pt); pClick->SendMessage(uiMsg, nFlags, MAKELPARAM(pt.x, pt.y)); } CWnd::OnLButtonDown(nFlags, point); } void CXTPTipWindow::OnLButtonDown(UINT nFlags, CPoint point) { ForwardMessage(WM_LBUTTONDOWN, nFlags, point); } void CXTPTipWindow::OnRButtonDown(UINT nFlags, CPoint point) { ForwardMessage(WM_RBUTTONDOWN, nFlags, point); } void CXTPTipWindow::OnMButtonDown(UINT nFlags, CPoint point) { ForwardMessage(WM_MBUTTONDOWN, nFlags, point); } void CXTPTipWindow::OnMouseMove(UINT /*nFlags*/, CPoint point) { ClientToScreen( &point ); if (!m_rectTipArea.IsRectNull() && !m_rectTipArea.PtInRect(point)) HideTipWindow(); } void CXTPTipWindow::SetTipText(LPCTSTR lpszTitle, LPCTSTR lpszDescrip, BOOL bRedraw) { m_strTitle = lpszTitle; //Single line // Avoid uggly outputed rectangle character in the tip window m_strDescrip = lpszDescrip; //Could be multi-line m_strDescrip.Remove(_T('\r')); if (bRedraw) { InvalidateRect(NULL); } } void CXTPTipWindow::SetTextTabStopPositions(LPINT pnTabStopPositions, int nTabPositions) { //pointer to integer array containing the tab stop positions (could be a static array) m_pnTabStopPositions = pnTabStopPositions; //number of array elements m_nTabPositions = nTabPositions; }