////////////////////////////////////////////////////////////////////////////
// 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 );

	CRect rect = m_rectDisplay;
	ScreenToClient(rect);

	dc.SetBkMode( TRANSPARENT ); 

	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);
}
