//////////////////////////////////////////////////////////////////////////// // TitleTip.cpp : implementation file // // Based on code by Zafir Anjum // // Adapted by Chris Maunder // Copyright (c) 1998-2002. All Rights Reserved. // // This code may be used in compiled form in any way you desire. This // file may be redistributed unmodified by any means PROVIDING it is // not sold for profit without the authors written consent, and // providing that this notice and the authors name and all copyright // notices remains intact. // // An email letting me know how you are using it would be nice as well. // // This file is provided "as is" with no expressed or implied warranty. // The author accepts no liability for any damage/loss of business that // this product may cause. // // For use with CGridCtrl v2.20+ // // History // 10 Apr 1999 Now accepts a LOGFONT pointer and // a tracking rect in Show(...) (Chris Maunder) // 18 Apr 1999 Resource leak in Show fixed by Daniel Gehriger // 8 Mar 2000 Added double-click fix found on codeguru // web site but forgot / can't find who contributed it // 28 Mar 2000 Aqiruse (marked with //FNA) // Titletips now use cell color // 18 Jun 2000 Delayed window creation added // ///////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "gridctrl.h" #ifndef GRIDCONTROL_NO_TITLETIPS #include "TitleTip.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CTitleTip CTitleTip::CTitleTip() { // Register the window class if it has not already been registered. WNDCLASS wndcls; HINSTANCE hInst = AfxGetInstanceHandle(); if(!(::GetClassInfo(hInst, TITLETIP_CLASSNAME, &wndcls))) { // otherwise we need to register a new class wndcls.style = CS_SAVEBITS; wndcls.lpfnWndProc = ::DefWindowProc; wndcls.cbClsExtra = wndcls.cbWndExtra = 0; wndcls.hInstance = hInst; wndcls.hIcon = NULL; wndcls.hCursor = LoadCursor( hInst, IDC_ARROW ); wndcls.hbrBackground = (HBRUSH)(COLOR_INFOBK +1); wndcls.lpszMenuName = NULL; wndcls.lpszClassName = TITLETIP_CLASSNAME; if (!AfxRegisterClass(&wndcls)) AfxThrowResourceException(); } m_dwLastLButtonDown = ULONG_MAX; m_dwDblClickMsecs = GetDoubleClickTime(); m_bCreated = FALSE; m_pParentWnd = NULL; } CTitleTip::~CTitleTip() { } BEGIN_MESSAGE_MAP(CTitleTip, CWnd) //{{AFX_MSG_MAP(CTitleTip) ON_WM_MOUSEMOVE() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CTitleTip message handlers BOOL CTitleTip::Create(CWnd * pParentWnd) { ASSERT_VALID(pParentWnd); // Already created? if (m_bCreated) return TRUE; DWORD dwStyle = WS_BORDER | WS_POPUP; DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST; m_pParentWnd = pParentWnd; m_bCreated = CreateEx(dwExStyle, TITLETIP_CLASSNAME, NULL, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL ); return m_bCreated; } BOOL CTitleTip::DestroyWindow() { m_bCreated = FALSE; return CWnd::DestroyWindow(); } // Show - Show the titletip if needed // rectTitle - The rectangle within which the original // title is constrained - in client coordinates // lpszTitleText - The text to be displayed // xoffset - Number of pixel that the text is offset from // left border of the cell void CTitleTip::Show(CRect rectTitle, LPCTSTR lpszTitleText, int xoffset /*=0*/, LPRECT lpHoverRect /*=NULL*/, const LOGFONT* lpLogFont /*=NULL*/, COLORREF crTextClr /* CLR_DEFAULT */, COLORREF crBackClr /* CLR_DEFAULT */) { if (!IsWindow(m_hWnd)) Create(m_pParentWnd); ASSERT( ::IsWindow( GetSafeHwnd() ) ); if (rectTitle.IsRectEmpty()) return; // If titletip is already displayed, don't do anything. if( IsWindowVisible() ) return; m_rectHover = (lpHoverRect != NULL)? lpHoverRect : rectTitle; m_rectHover.right++; m_rectHover.bottom++; m_pParentWnd->ClientToScreen( m_rectHover ); ScreenToClient( m_rectHover ); // Do not display the titletip is app does not have focus if( GetFocus() == NULL ) return; CClientDC dc(this); CString strTitle = _T(""); strTitle += _T(" "); strTitle += lpszTitleText; strTitle += _T(" "); CFont font, *pOldFont = NULL; if (lpLogFont) { font.CreateFontIndirect(lpLogFont); pOldFont = dc.SelectObject( &font ); } else { // use same font as ctrl pOldFont = dc.SelectObject( m_pParentWnd->GetFont() ); } // ¸ÖƼ¶óÀÎÀÌ ¸î°³? °¡Àå±ä ¹°ÀÚ¿­Àº ¾î¶²°Í? // Test Code by Ringo ---------------- TCHAR szMaxTXT[ 256 ]; // ÃÖ°í±ä ¹®ÀÚ¿­ TCHAR szTmp [ 256 ]; // ¹®ÀÚ¿­ Àӽú¸°üÇÔ TCHAR* szTitleTip = strTitle.GetBuffer(); TCHAR* nPos = szTitleTip; TCHAR* nPosTXT = szMaxTXT; TCHAR* szTmpTXT = szTmp; int LineCnt = 0; ZeroMemory( szMaxTXT, sizeof( szMaxTXT ) ); while( *nPos != '\0' ) { if( ( *nPos == '\n' ) || ( *(nPos + 1 ) == '\0' ) ) { if( _tcslen( szMaxTXT ) < _tcslen( szTmp ) ) { //_strncpy( szMaxTXT, szTmp, strlen( szTmp ) ); CopyMemory( szMaxTXT, szTmp, _tcslen( szTmp ) ); szTmpTXT = szTmp; } LineCnt ++; } else { *szTmpTXT = *nPos; szTmpTXT ++; *szTmpTXT = 0; } nPos ++; } //------------------------------------ // Define the rectangle outside which the titletip will be hidden. // We add a buffer of one pixel around the rectangle m_rectTitle.top = -1; m_rectTitle.left = -xoffset-1; m_rectTitle.right = rectTitle.Width()-xoffset; m_rectTitle.bottom = rectTitle.Height() * ( LineCnt + 1 ); CString strMax = szMaxTXT; CSize size = dc.GetTextExtent( strMax ); TEXTMETRIC tm; dc.GetTextMetrics(&tm); size.cx += tm.tmOverhang; // Determine the width of the text rectTitle.bottom = rectTitle.top + ( ( size.cy + 3 ) * ( LineCnt + 1 ) ); // +3ÀÇ Àǹ̴ ÁÙ°£°ÝÀ» 3Çȼ¿·Î ´ëÃæ~~ ¤Ñ.¤Ñ¤» m_pParentWnd->ClientToScreen( rectTitle ); CRect rectDisplay = rectTitle; rectDisplay.left += xoffset; rectDisplay.right = rectDisplay.left + size.cx + xoffset; // Do not display if the text fits within available space if ( rectDisplay.right > rectTitle.right-xoffset ) { // Show the titletip SetWindowPos( &wndTop, rectDisplay.left, rectDisplay.top, rectDisplay.Width() + 10, rectDisplay.Height() - size.cy, // + 10ÀÇ ÀǹÌ? À½.. ¹®ÀÚ¿­±æÀ̰¡ Á¤È®ÇÏ°Ô »ÌÈ÷Áö ¾Ê´Â´Ù ´Ù¸¥Ã³¸®¸¦ Á¶±Ý´õ ÇØÁà¾ßÇÑ´Ù. SWP_SHOWWINDOW|SWP_NOACTIVATE ); // FNA - handle colors correctly if (crBackClr != CLR_DEFAULT) { CBrush backBrush(crBackClr); CBrush* pOldBrush = dc.SelectObject(&backBrush); CRect rect; dc.GetClipBox(&rect); // Erase the area needed dc.PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY); dc.SelectObject(pOldBrush); } // Set color if (crTextClr != CLR_DEFAULT)//FNA dc.SetTextColor(crTextClr);//FA dc.SetBkMode( TRANSPARENT ); // ¸ÖƼ¶óÀÎ ±×¸®±â.... // ¤»¤» ¿ª½Ã ¿©±âµµ ³¯¸² ¤Ñ.¤Ñ --------------------------------- nPos = szTitleTip; nPosTXT = szMaxTXT; szTmpTXT = szTmp; LineCnt = 0; while( *nPos != '\0' ) { if( ( *nPos == '\n' ) || ( *(nPos + 1 ) == '\0' ) ) { dc.TextOut( 0, LineCnt * ( size.cy + 3 ), szTmp ); szTmpTXT = szTmp; *szTmpTXT = 0; LineCnt ++; } else { *szTmpTXT = *nPos; szTmpTXT ++; *szTmpTXT = 0; } nPos ++; } //---------------------------------------------------------------- SetCapture(); } dc.SelectObject( pOldFont ); } void CTitleTip::Hide() { if (!::IsWindow(GetSafeHwnd())) return; if (GetCapture()->GetSafeHwnd() == GetSafeHwnd()) ReleaseCapture(); ShowWindow( SW_HIDE ); } void CTitleTip::OnMouseMove(UINT nFlags, CPoint point) { if (!m_rectHover.PtInRect(point)) { Hide(); // Forward the message ClientToScreen( &point ); CWnd *pWnd = WindowFromPoint( point ); if ( pWnd == this ) pWnd = m_pParentWnd; int hittest = (int)pWnd->SendMessage(WM_NCHITTEST,0,MAKELONG(point.x,point.y)); if (hittest == HTCLIENT) { pWnd->ScreenToClient( &point ); pWnd->PostMessage( WM_MOUSEMOVE, nFlags, MAKELONG(point.x,point.y) ); } else { pWnd->PostMessage( WM_NCMOUSEMOVE, hittest, MAKELONG(point.x,point.y) ); } } } BOOL CTitleTip::PreTranslateMessage(MSG* pMsg) { // Used to qualify WM_LBUTTONDOWN messages as double-clicks DWORD dwTick=0; BOOL bDoubleClick=FALSE; CWnd *pWnd; int hittest; switch (pMsg->message) { case WM_LBUTTONDOWN: // Get tick count since last LButtonDown dwTick = GetTickCount(); bDoubleClick = ((dwTick - m_dwLastLButtonDown) <= m_dwDblClickMsecs); m_dwLastLButtonDown = dwTick; // NOTE: DO NOT ADD break; STATEMENT HERE! Let code fall through case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: { POINTS pts = MAKEPOINTS( pMsg->lParam ); POINT point; point.x = pts.x; point.y = pts.y; ClientToScreen( &point ); Hide(); pWnd = WindowFromPoint( point ); if (!pWnd) return CWnd::PreTranslateMessage(pMsg); if( pWnd->GetSafeHwnd() == GetSafeHwnd()) pWnd = m_pParentWnd; hittest = (int)pWnd->SendMessage(WM_NCHITTEST,0,MAKELONG(point.x,point.y)); if (hittest == HTCLIENT) { pWnd->ScreenToClient( &point ); pMsg->lParam = MAKELONG(point.x,point.y); } else { switch (pMsg->message) { case WM_LBUTTONDOWN: pMsg->message = WM_NCLBUTTONDOWN; break; case WM_RBUTTONDOWN: pMsg->message = WM_NCRBUTTONDOWN; break; case WM_MBUTTONDOWN: pMsg->message = WM_NCMBUTTONDOWN; break; } pMsg->wParam = hittest; pMsg->lParam = MAKELONG(point.x,point.y); } // If this is the 2nd WM_LBUTTONDOWN in x milliseconds, // post a WM_LBUTTONDBLCLK message instead of a single click. pWnd->PostMessage( bDoubleClick ? WM_LBUTTONDBLCLK : pMsg->message, pMsg->wParam, pMsg->lParam); return TRUE; } case WM_KEYDOWN: case WM_SYSKEYDOWN: Hide(); m_pParentWnd->PostMessage( pMsg->message, pMsg->wParam, pMsg->lParam ); return TRUE; } if( GetFocus() == NULL ) { Hide(); return TRUE; } return CWnd::PreTranslateMessage(pMsg); } #endif // GRIDCONTROL_NO_TITLETIPS