////////////////////////////////////////////////////////////////////////////
|
// TitleTip.cpp : implementation file
|
//
|
// Adapted from code written by Zafir Anjum
|
//
|
// Modifed 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
|
// 7 Jan 2000 Added multiline capabilities, and the ability to
|
// specify the maximum length of the tip (Mark Findlay)
|
|
|
#include "stdafx.h"
|
#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();
|
}
|
}
|
|
CTitleTip::~CTitleTip()
|
{
|
if (::IsWindow(m_hWnd))
|
DestroyWindow();
|
}
|
|
|
BEGIN_MESSAGE_MAP(CTitleTip, CWnd)
|
//{{AFX_MSG_MAP(CTitleTip)
|
ON_WM_MOUSEMOVE()
|
ON_WM_PAINT()
|
ON_WM_SYSKEYDOWN()
|
ON_WM_KEYDOWN()
|
//}}AFX_MSG_MAP
|
END_MESSAGE_MAP()
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
// CTitleTip message handlers
|
|
BOOL CTitleTip::Create(CWnd * pParentWnd)
|
{
|
// ASSERT_VALID(pParentWnd);
|
|
DWORD dwStyle = WS_BORDER | WS_POPUP;
|
DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
|
m_pParentWnd = pParentWnd;
|
|
return CreateEx(dwExStyle, TITLETIP_CLASSNAME, NULL, dwStyle,
|
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
NULL, NULL, NULL );
|
}
|
|
|
// 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*/, int nMaxChars /*=-1*/,
|
LPRECT lpHoverRect /*=NULL*/,
|
LPLOGFONT lpLogFont /*=NULL*/,
|
DWORD dwFormat /*=...*/)
|
{
|
// 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;
|
|
// 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()+1;
|
|
// Determine the width of the text
|
m_pParentWnd->ClientToScreen( rectTitle );
|
|
CClientDC dc(this);
|
m_strTitle = _T("");
|
//m_strTitle += _T(" ");
|
m_strTitle += lpszTitleText;
|
//m_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() );
|
}
|
|
CSize size = dc.GetTextExtent( m_strTitle );
|
|
TEXTMETRIC tm;
|
dc.GetTextMetrics(&tm);
|
size.cx += tm.tmOverhang;
|
|
dc.SelectObject( pOldFont );
|
|
m_rectDisplay = rectTitle;
|
m_rectDisplay.left += xoffset-1;
|
m_rectDisplay.top += 0;
|
m_rectDisplay.right = m_rectDisplay.left + size.cx + xoffset;
|
m_rectDisplay.bottom = m_rectDisplay.top + size.cy;
|
|
// Do not display if the text fits within available space
|
if ( m_rectDisplay.right <= rectTitle.right-xoffset )
|
return;
|
|
// We will use avg char width to set max tooltip width
|
int nMaxTooltipWidth = -1;
|
if (nMaxChars > 0)
|
{
|
int nMaxTooltipWidth = (tm.tmAveCharWidth * nMaxChars);
|
if (nMaxTooltipWidth < 0)
|
nMaxTooltipWidth *= -1;
|
|
// Rect display to be set to max chars
|
if (m_rectDisplay.Width() > nMaxTooltipWidth)
|
m_rectDisplay.right = m_rectDisplay.left + nMaxTooltipWidth;
|
}
|
|
//***************************************************************************************
|
//Adjust the dimensions of the rect to fit within the client
|
|
// Get the coordinates of the parents client area. (In this case the ListView's client
|
// area) and convert coordinates to those of the tooltip.
|
CRect rectClient;
|
m_pParentWnd->GetClientRect( rectClient );
|
m_pParentWnd->ClientToScreen( rectClient );
|
|
|
// ------------------------------------------------------------------------------
|
// Use the screen's right edge as the right hand border, not the right edge of the client.
|
// You can comment this out to use the right client as the border.
|
CWindowDC wdc(NULL);
|
rectClient.right = GetDeviceCaps(wdc, HORZRES) - 8;
|
rectClient.bottom = GetDeviceCaps(wdc, VERTRES) - 8;
|
//---------------------------------------------------------------------------------------
|
|
|
//If the right edge exceeds the right edge of the client:
|
// see how much room there is to move the display to the left and adjust the
|
// rectangle that far to the left. If the rect still exceeds the right edge, clip
|
// the right edge to match the client right edge.
|
//
|
// Does the right display edge exceed the right client edge?
|
if (m_rectDisplay.right > rectClient.right)
|
{
|
// establish what is available left shift wise and what is needed
|
int nAvail = 0;
|
int nNeeded = m_rectDisplay.right - rectClient.right;
|
|
if (m_rectDisplay.left > rectClient.left)
|
nAvail = m_rectDisplay.left - rectClient.left;
|
|
// is there room to move left?
|
if (nAvail >= nNeeded)
|
{
|
m_rectDisplay.OffsetRect(-nNeeded,0); // yes, move all that is needed
|
// increase the size of the window that will be inspected to see if the
|
// cursor has gone outside of the tooltip area by the number of units we
|
// offset our display rect.
|
m_rectTitle.right += nNeeded;
|
}
|
else
|
{
|
m_rectDisplay.OffsetRect(-nAvail,0); // no, at least move to left edge of client
|
// increase the size of the window that will be inspected to see if the
|
// cursor has gone outside of the tooltip area by the number of units we
|
// offset our display rect.
|
m_rectTitle.right += nAvail;
|
}
|
|
// Did we move enough? If not, clip right edge to match client right edge
|
if (m_rectDisplay.right > rectClient.right)
|
m_rectDisplay.right = rectClient.right;
|
}
|
|
|
//If the left edge exceeds the left edge of the client:
|
// see how much room there is to move the display to the right and adjust the
|
// rectangle that far to the right. If the rect still exceeds the left edge, clip
|
// the left edge to match the client left edge.
|
//
|
// Does the left display edge exceed the left client edge?
|
if (m_rectDisplay.left < rectClient.left)
|
{
|
// establish what is available right shift wise and what is needed
|
int nAvail = 0;
|
int nNeeded = rectClient.left - m_rectDisplay.left;
|
|
if (m_rectDisplay.right < rectClient.right)
|
nAvail = rectClient.right - m_rectDisplay.right;
|
|
// is there room to move left?
|
if (nAvail >= nNeeded)
|
{
|
m_rectDisplay.OffsetRect(+nNeeded,0); // yes, move all that is needed
|
// increase the size of the window that will be inspected to see if the
|
// cursor has gone outside of the tooltip area by the number of units we
|
// offset our display rect.
|
m_rectTitle.left -= nNeeded;
|
}
|
else
|
{
|
m_rectDisplay.OffsetRect(+nAvail,0); // no, at least move to left edge of client
|
// increase the size of the window that will be inspected to see if the
|
// cursor has gone outside of the tooltip area by the number of units we
|
// offset our display rect.
|
m_rectTitle.left -= nAvail;
|
}
|
|
// Did we move enough? If not, clip left edge to match client left edge
|
if (m_rectDisplay.left < rectClient.left)
|
m_rectDisplay.left = rectClient.left;
|
}
|
|
|
// if the calculated width > maxwidth set above then truncate
|
if (nMaxTooltipWidth > 0 && m_rectDisplay.Width() > nMaxTooltipWidth)
|
m_rectDisplay.right = m_rectDisplay.left + nMaxTooltipWidth;
|
|
//***************************************************************************************
|
|
// Use a "work" rect to calculate the bottom. This work rect will be inset
|
// slightly from the rect we have just created so the tooltip does not touch
|
// the sides.
|
CRect rectCalc = m_rectDisplay;
|
|
// rectCalc.top += 1;
|
|
int nHeight = dc.DrawText(m_strTitle, rectCalc, dwFormat | DT_CALCRECT);
|
m_dwFormat = dwFormat;
|
|
// If this is a single line, shorten the display to get rid of any excess blank space
|
if (nHeight == tm.tmHeight)
|
{
|
rectCalc.right = rectCalc.left + size.cx + 3;
|
}
|
|
|
m_rectDisplay.bottom = m_rectDisplay.top + nHeight;
|
|
// ensure the tooltip does not exceed the bottom of the screen
|
if (m_rectDisplay.bottom > rectClient.bottom)
|
{
|
m_rectDisplay.bottom = rectClient.bottom;
|
rectCalc.bottom = rectClient.bottom;
|
}
|
|
SetWindowPos( &wndTop,
|
m_rectDisplay.left,
|
m_rectDisplay.top,
|
m_rectDisplay.Width(),
|
m_rectDisplay.Height(),
|
SWP_SHOWWINDOW|SWP_NOACTIVATE );
|
SetCapture();
|
}
|
|
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)
|
{
|
CWnd *pWnd;
|
int hittest;
|
switch (pMsg->message)
|
{
|
case WM_LBUTTONDOWN:
|
case WM_RBUTTONDOWN:
|
case WM_MBUTTONDOWN:
|
POINTS pts = MAKEPOINTS( pMsg->lParam );
|
POINT point;
|
point.x = pts.x;
|
point.y = pts.y;
|
ClientToScreen( &point );
|
pWnd = WindowFromPoint( point );
|
if( pWnd == this )
|
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);
|
}
|
|
Hide();
|
pWnd->PostMessage(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);
|
}
|
|
void CTitleTip::OnPaint()
|
{
|
CPaintDC dc(this); // device context for painting
|
|
TEXTMETRIC tm;
|
dc.GetTextMetrics(&tm);
|
|
CFont *pFont = m_pParentWnd->GetFont(); // use same font as ctrl
|
CFont *pFontDC = dc.SelectObject( pFont );
|
int nHeight=0;
|
|
CRect rect = m_rectDisplay;
|
ScreenToClient(rect);
|
|
dc.SetBkMode( TRANSPARENT );
|
|
nHeight = dc.DrawText(m_strTitle, rect, m_dwFormat);
|
|
dc.SelectObject( pFontDC );
|
|
// Do not call CWnd::OnPaint() for painting messages
|
}
|
|
void CTitleTip::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
|
{
|
Hide();
|
CWnd::OnSysKeyDown(nChar, nRepCnt, nFlags);
|
}
|
|
void CTitleTip::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
|
{
|
Hide();
|
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
|
}
|