// TabCtrlEx.cpp : implementation file
//

#include "stdafx.h"
#include "TabCtrlEx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif



/////////////////////////////////////////////////////////////////////////////
// CTabCtrlEx

CTabCtrlEx::CTabCtrlEx()
{
    m_CrossDrawer.Init();
    _Init();
}

CTabCtrlEx::~CTabCtrlEx()
{
}


BEGIN_MESSAGE_MAP(CTabCtrlEx, CTabCtrl)
    //{{AFX_MSG_MAP(CTabCtrlEx)
    ON_WM_PAINT()
    ON_WM_MOUSEMOVE()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
    ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTabCtrlEx message handlers

void CTabCtrlEx::OnPaint() 
{
    
    // TODO: Add your message handler code here
//    CPaintDC dc(this);
    Default();
    CClientDC dc(this); // device context for painting
    DrawDefaultTabItem(&dc);
    
    // Do not call CTabCtrl::OnPaint() for painting messages
}

void CTabCtrlEx::OnMouseMove(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    if( !m_bTracking )
    {
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.hwndTrack = GetSafeHwnd();
        tme.dwFlags = TME_LEAVE;
        tme.dwHoverTime = 0;
        m_bTracking = _TrackMouseEvent(&tme);
    }
    
    _DoOnMouseMove(nFlags, point);

    CTabCtrl::OnMouseMove(nFlags, point);
}

void CTabCtrlEx::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    if( 1 )
    {
        TCHITTESTINFO hti;    hti.flags = 0;
        hti.pt = point;
        
        int ixHot = HitTest(&hti);
        if( ixHot >= 0 )
        {
            CRect rcCross;
            CSize sizeBitmap;
            
            m_CrossDrawer.GetBitmapSize(sizeBitmap);
            rcCross = _GetCrossRectInItem(ixHot, sizeBitmap);
            
            if( rcCross.PtInRect(point) )
            {
                SetCapture();
                m_bMousePressed = TRUE;

                m_iSelTabIndex = m_iCurTabIndex = ixHot;
                m_rcCurCrossArea = m_rcSelCrossArea = rcCross;

                InvalidateRect(&m_rcCurCrossArea);
            }
        }
    }
    
    CTabCtrl::OnLButtonDown(nFlags, point);    

}

LRESULT CTabCtrlEx::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
    m_bHover = FALSE;
    m_bTracking = FALSE;

    TRACE("OnMouseLeave\n");

    CPoint point;
    if( GetCursorPos(&point) )
    {
        ScreenToClient(&point);
        _DoOnMouseMove(0, point);
        InvalidateRect(&m_rcCurCrossArea);
    }
    
    return 0;
}

LRESULT CTabCtrlEx::OnMouseHover(WPARAM wParam, LPARAM lParam)
{
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp)                        ((int)(short)LOWORD(lp))
#endif //
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lp)                        ((int)(short)HIWORD(lp))
#endif 
    return 0;
}

void CTabCtrlEx::OnLButtonUp(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    CTabCtrl::OnLButtonUp(nFlags, point);
    if( m_bMousePressed )
    {
        TRACE("CTabCtrlEx::OnLButtonUp() \n");
        m_bMousePressed = FALSE;
        ReleaseCapture();
//        InvalidateRect(NULL);
        
        int iSelTabIndex = m_iSelTabIndex;

        m_rcSelCrossArea.SetRectEmpty();
        m_iSelTabIndex = -1;
        
        InvalidateRect(&m_rcSelCrossArea);
        if( iSelTabIndex == _ValidTabItem(point) )
        {
            FireDeleteTabItemNotify(iSelTabIndex);
        }
    }
    m_bMousePressed = FALSE;
    
}

BOOL CTabCtrlEx::_Init()
{
    m_iExpandWith = 0;
    m_iExpandTailSpace = 0;
    m_iCurTabIndex = -1;
    m_rcCurCrossArea.SetRectEmpty();

    m_bHover = FALSE;
    m_bTracking = FALSE;
    m_bMousePressed = FALSE;
    m_iSelTabIndex = -1;
    m_rcSelCrossArea.SetRectEmpty();

    return TRUE;
}

CString CTabCtrlEx::_RepeatChar(CString str, int count)
{
    CString strR;
    for( int i = 0; i < count; i++ )
        strR += str;
    return strR;
}

BOOL CTabCtrlEx::_CalcExpand(CDC* pDC)
{
     CSize s(0, 50);
    
    int iExpand = 0;
    CSize sizeExtent;
    CString strTestText = _T(" ");
    sizeExtent = pDC->GetOutputTextExtent(strTestText);
    CSize size1 = pDC->GetTextExtent(strTestText);

    CSize sizeBitmap;
    m_CrossDrawer.GetBitmapSize(sizeBitmap);
    iExpand =  sizeBitmap.cx / sizeExtent.cx + 1;
    if( iExpand == m_iExpandTailSpace ) return FALSE;

    m_iExpandWith = iExpand * sizeExtent.cx;
    m_iExpandTailSpace = iExpand;
    return TRUE;
}


void CTabCtrlEx::_RefromatTabTitles()
{
    CString strSpace = _RepeatChar(_T(" "), m_iExpandTailSpace);
    CString strText;
    TCITEM tci;
    int iCount = GetItemCount();
    for( int i = 0; i < iCount; i++ )
    {
        strText = m_astrOriginalTabTitles[i] + strSpace;
        tci.mask = TCIF_TEXT;
        tci.pszText = (LPTSTR)(LPCTSTR)strText;
        CTabCtrl::SetItem(i, &tci);
    }
}

BOOL CTabCtrlEx::InsertItem(int nItem, LPCTSTR lpszItem)
{
    return CTabCtrlEx::InsertItem(nItem, lpszItem, -1);
}

BOOL CTabCtrlEx::InsertItem(int nItem, LPCTSTR lpszItem, int nImage)
{
    int iIndex;
    CString strText = lpszItem;
    strText += _RepeatChar(_T(" "), m_iExpandTailSpace);

    iIndex = CTabCtrl::InsertItem(nItem, (LPCTSTR)strText, nImage);
    if( iIndex >= 0 )
    {
        CString strItem = lpszItem;
        m_astrOriginalTabTitles.InsertAt(iIndex, lpszItem);
    }

    return iIndex;
}

HWND CTabCtrlEx::_GetAdjustCtrlHwnd()
{
    HWND hWnd = NULL;
    hWnd = ::GetWindow(GetSafeHwnd(), GW_CHILD);
    while( hWnd )
    {
        if( 1 == ::GetDlgCtrlID(hWnd) )
            break;
        ::GetWindow(hWnd, GW_HWNDNEXT);
    }

    return hWnd;
}

void CTabCtrlEx::DrawDefaultTabItem(CDC* pDC)
{
    if( _CalcExpand(pDC) )
        _RefromatTabTitles();

    CRect rcAdjust;
    HWND hAdjustWnd = _GetAdjustCtrlHwnd();
    if( hAdjustWnd )
    {
        ::GetWindowRect(hAdjustWnd, &rcAdjust);
        ScreenToClient(&rcAdjust);
    }

    CRect rcTabCtrl, rcItem, rcCross;
    CSize sizeBitmap;

    int nTab = GetItemCount();
    if( !nTab ) return;

    m_CrossDrawer.GetBitmapSize(sizeBitmap);

    GetClientRect(&rcTabCtrl);

    int iStatus;
    CPoint ptCrossCenter;
    TCHITTESTINFO hti;    hti.flags = 0;
    for( int ixTab = 0; ixTab < nTab; ixTab++ )
    {
        if( !GetItemRect(ixTab, &rcItem) )
            continue;
        if( rcItem.right < rcTabCtrl.left || (hAdjustWnd && ::IsWindowVisible(hAdjustWnd) && (rcItem.right >= rcAdjust.left)) )
            continue;

        //_DrawTabItem(pDC, ixTab);
        iStatus = _GetCrossButtonStatus(ixTab);
        _DrawCrossButton(pDC, rcItem, (ixTab == GetCurSel()), iStatus);
    }
}

void CTabCtrlEx::_DrawCrossButton(CDC* pDC, int iItem)
{
    CRect rcItem, rcCross;
    CSize sizeBitmap;
    int iStatus;
    
    if( iItem >= 0 )
    {
        GetItemRect(iItem, &rcItem);
        
        iStatus = _GetCrossButtonStatus(iItem);
        _DrawCrossButton(pDC, rcItem, (iItem == GetCurSel()), iStatus);
    }
}

void CTabCtrlEx::_DrawCrossButton(CDC* pDC, const CRect& rcItem, BOOL bCur, int iCrossStatus)
{
    CRect rcCross;
    CSize sizeBitmap;

    m_CrossDrawer.GetBitmapSize(iCrossStatus, sizeBitmap);

    rcCross = _GetCrossRectInItem(rcItem, sizeBitmap, bCur);

    m_CrossDrawer.Draw(pDC, rcCross, iCrossStatus);
}

inline int CTabCtrlEx::_GetCrossButtonStatus(int iItem)
{
    if( m_bMousePressed )
    {
        if( iItem == m_iSelTabIndex ) 
            return CCrossDrawer::ePressed;
    }
    else
    {
        if( iItem == m_iCurTabIndex )
            return CCrossDrawer::eHot;
    }
    return CCrossDrawer::eNormal;

}

CRect CTabCtrlEx::_GetCrossRectInItem(int iIndex, const CSize& sizeBitmap)
{
    CRect rcItem, rcCross;

    rcCross.SetRectEmpty();

    if( !GetItemRect(iIndex, &rcItem) )
        return rcCross;

    return _GetCrossRectInItem(rcItem, sizeBitmap, (iIndex == GetCurSel()));
}

CRect CTabCtrlEx::_GetCrossRectInItem(const CRect& rcItem, const CSize& sizeBitmap, BOOL bCur)
{
    CRect rcCross;
    CSize sizeCross;

    rcCross.SetRectEmpty();

    sizeCross.cx = __min((rcItem.Height() - 2), sizeBitmap.cx);
    sizeCross.cy = __min((rcItem.Height() - 2), sizeBitmap.cy);
    
    rcCross.top = rcItem.top + (rcItem.Height() + 2 - sizeCross.cy) / 2;
    rcCross.right = rcItem.right - 4;
    rcCross.bottom = rcCross.top + sizeCross.cy;
    rcCross.left = rcCross.right - sizeCross.cx;
    
    if( bCur )
        rcCross.OffsetRect(1, -1);
    
    return rcCross;
}

int CTabCtrlEx::_ValidTabItem(CPoint& point)
{
    TCHITTESTINFO hti;    hti.flags = 0;
    hti.pt = point;
    
    int ixHot = HitTest(&hti);
    if( ixHot >= 0 )
    {
        CRect rcCross;
        CSize sizeBitmap;
        
        m_CrossDrawer.GetBitmapSize(sizeBitmap);
        rcCross = _GetCrossRectInItem(ixHot, sizeBitmap);
        
        if( rcCross.PtInRect(point) )
            return ixHot;
    }

    return -1;
}

LRESULT CTabCtrlEx::_DoOnMouseMove(UINT nFlags, CPoint point)
{
    TRACE("_DoOnMouseMove()\n");
    
    if( !m_rcCurCrossArea.IsRectEmpty() )
    {
        if( !m_rcCurCrossArea.PtInRect(point) )
        {
            m_iCurTabIndex = -1;
            if( !m_bMousePressed )
                InvalidateRect(&m_rcCurCrossArea);
            m_rcCurCrossArea.SetRectEmpty();
        }
    }
    else
    {
        TCHITTESTINFO hti;    hti.flags = 0;
        hti.pt = point;
        
        int ixHot = HitTest(&hti);
        if( ixHot >= 0 )
        {
            CRect rcCross;
            CSize sizeBitmap;
            
            m_CrossDrawer.GetBitmapSize(sizeBitmap);
            rcCross = _GetCrossRectInItem(ixHot, sizeBitmap);
            
            if( rcCross.PtInRect(point) )
            {
                m_rcCurCrossArea = rcCross;
                m_iCurTabIndex = ixHot;
                
                if( !m_bMousePressed )
                    InvalidateRect(&m_rcCurCrossArea);
            }
        }
    }

    return 0;
}

void CTabCtrlEx::FireDeleteTabItemNotify(int iItem)
{
    UINT uId = GetWindowLong(GetSafeHwnd(), GWL_ID);
    TCNMDELETEITEM nmdr;
    memset(&nmdr, 0, sizeof(nmdr));
    nmdr.hdr.code = TCN_DELETEITEM;
    nmdr.hdr.hwndFrom = GetSafeHwnd();
    nmdr.hdr.idFrom = uId;
    nmdr.iDeleteItem = iItem;

    CWnd *pParentWnd = GetParent();
    if( pParentWnd )
    {
        if( pParentWnd->SendMessage(WM_NOTIFY, (WPARAM)uId, (LPARAM)&nmdr) )
        {
            DeleteItem(iItem);

            if( iItem >= GetItemCount() )
                iItem = GetItemCount() - 1;

            if( iItem >= 0 )
            {
                SetCurSel(iItem);

                NMHDR hdr;
                hdr.hwndFrom = GetSafeHwnd();
                hdr.code = TCN_SELCHANGE;
                hdr.idFrom = uId;
                pParentWnd->SendMessage(WM_NOTIFY, (WPARAM)uId, (LPARAM)&hdr);
            }
        }
    }
}

BOOL CTabCtrlEx::DeleteItem(int iItem)
{
    BOOL bRet = CTabCtrl::DeleteItem(iItem);
    if( bRet )
    {
        m_astrOriginalTabTitles.RemoveAt(iItem);
        
        HWND hWnd = _GetAdjustCtrlHwnd();
        if( hWnd && ::IsWindowVisible(hWnd) )
        {
            UINT uID = ::GetWindowLong(hWnd, GWL_ID);
            int iMax = ::SendMessage(hWnd, UDM_GETRANGE, (WPARAM)0, (LPARAM)0);
            int iPos = ::SendMessage(hWnd, UDM_GETPOS, (WPARAM)0, (LPARAM)0);

            NMUPDOWN hdr;
            hdr.hdr.code = UDN_DELTAPOS;
            hdr.hdr.hwndFrom = hWnd;
            hdr.hdr.idFrom = 
            hdr.iPos = iPos;
            hdr.iDelta = 1;

            SendMessage(WM_NOTIFY, (WPARAM)uID, (LPARAM)&hdr);
            SendMessage(WM_HSCROLL, (WPARAM)MAKEWPARAM(SB_THUMBPOSITION, iPos), (LPARAM)hWnd);
            SendMessage(WM_HSCROLL, (WPARAM)MAKEWPARAM(SB_ENDSCROLL, iPos), (LPARAM)hWnd);
        }
    }

    return bRet;
}

BOOL CTabCtrlEx::DeleteAllItems()
{
    BOOL bRet = CTabCtrl::DeleteAllItems();
    if( bRet )
    {
        m_astrOriginalTabTitles.RemoveAll();
    }
    
    return bRet;
}