Click here to Skip to main content
15,885,767 members
Articles / Desktop Programming / MFC

Resource ID Organiser Add-In for Visual C++ 5.0/6.0/.NET

Rate me:
Please Sign up or sign in to vote.
4.98/5 (71 votes)
10 Jan 2005CPOL25 min read 530.6K   12.1K   201  
An application/add-in to organise and renumber resource symbol IDs
// FlatTab.cpp : implementation file
//
// Copyright � 1999 Ian Brumby
//
// This source code may be used in compiled form in any way you desire. 
// Source file(s) may be redistributed unmodified by any means PROVIDING
// they are not sold for profit without the authors expressed written consent,
// and providing that this notice and the authors name and all copyright
// notices remain intact.
//
// ==========================================================================  
// HISTORY:	  
// ==========================================================================  
//			1.4 	21 Jul 1999	- Initial release.
//			1.5		30 Aug 1999 - Several enhancements by Phil Baxter 
//					<Phil.Baxter@mrjinx.demon.co.uk>
// ==========================================================================  

// Excel uses FF_ROMAN and Font Height = ~(control's height - 4)
// Excel draws its tabs right to left
// Excel has 3DLIGHT highlighting on the left diagonal
// Excel has home/end buttons
// Excel's buttons change width as the control's height changes
//
/////////////////////////////////////////////////////////////////////////////
/****************************************************************************
 *
 * $Date: 5/03/01 15:50 $
 * $Revision: 2 $
 * $Archive: /Projects/AddIns/ResOrg/CJLibrary/CJLibrary/CJFlatTabCtrl.cpp $
 *
 * $History: CJFlatTabCtrl.cpp $
 * 
 * *****************  Version 2  *****************
 * User: Andy         Date: 5/03/01    Time: 15:50
 * Updated in $/Projects/AddIns/ResOrg/CJLibrary/CJLibrary
 * Bug fixes
 * 
 * *****************  Version 11  *****************
 * User: Kirk Stowell Date: 10/26/99   Time: 10:52p
 * Updated in $/CodeJock/CJLibrary
 * Fixed bug with flat tab where in some cases tabs were not completely
 * painted.
 * 
 * *****************  Version 10  *****************
 * User: Kirk Stowell Date: 10/25/99   Time: 10:52p
 * Updated in $/CodeJock/CJLibrary
 * Modified resource include for static builds.
 * 
 * *****************  Version 9  *****************
 * User: Kirk Stowell Date: 10/24/99   Time: 12:15a
 * Updated in $/CodeJock/CJLibrary
 * Fixed minor bug with tab font creation.
 * 
 * *****************  Version 8  *****************
 * User: Kirk Stowell Date: 10/24/99   Time: 12:01a
 * Updated in $/CodeJock/CJLibrary
 * Fixed potential resource and memory leak problems.
 * 
 * *****************  Version 7  *****************
 * User: Kirk Stowell Date: 10/14/99   Time: 11:37p
 * Updated in $/CodeJock/CJLibrary
 * Fixed bug with flat tab control that would have kept one from disabling
 * the l/r arrow buttons, which caused an exception to be thrown. Thanks
 * to Ted Crow [crowtc@surge.net] for help with this.
 * 
 * *****************  Version 6  *****************
 * User: Kirk Stowell Date: 10/14/99   Time: 12:25p
 * Updated in $/CodeJock/CJLibrary
 * Added source control history to file header.
 *
 ***************************************************************************/
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "CJResource.h"
#include "CJFlatTabCtrl.h"

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

/////////////////////////////////////////////////////////////////////////////
// CCJFlatTabCtrl

CCJFlatTabCtrl::CCJFlatTabCtrl()
{
	m_bHasArrows          = false;
	m_bHasHomeEnd         = false;
	m_bTabsOnBottom       = false;
	m_iCurSel             = -1;
	m_pLeftButton         = NULL;
	m_pRightButton        = NULL;
	m_pHomeButton         = NULL;
	m_pEndButton          = NULL;
	m_hLeftArrow          = NULL;
	m_hLeftArrowDisabled  = NULL;
	m_hRightArrow         = NULL;
	m_hRightArrowDisabled = NULL;
	m_hHomeArrow          = NULL;
	m_hHomeArrowDisabled  = NULL;
	m_hEndArrow           = NULL;
	m_hEndArrowDisabled   = NULL;
	m_iXOffset            = 0;
}

CCJFlatTabCtrl::~CCJFlatTabCtrl()
{
	_delete(m_pLeftButton);
	_delete(m_pRightButton);
	_delete(m_pHomeButton);
	_delete(m_pEndButton);

	// free HENHMETAFILE handles.
	if(m_hLeftArrow) {
		::DeleteEnhMetaFile(m_hLeftArrow);
	}
	if(m_hLeftArrowDisabled) {
		::DeleteEnhMetaFile(m_hLeftArrowDisabled);
	}
	if(m_hRightArrow) {
		::DeleteEnhMetaFile(m_hRightArrow);
	}
	if(m_hRightArrowDisabled) {
		::DeleteEnhMetaFile(m_hRightArrowDisabled);
	}
	if(m_hHomeArrow) {
		::DeleteEnhMetaFile(m_hHomeArrow);
	}
	if(m_hHomeArrowDisabled) {
		::DeleteEnhMetaFile(m_hHomeArrowDisabled);
	}
	if(m_hEndArrow) {
		::DeleteEnhMetaFile(m_hEndArrow);
	}
	if(m_hEndArrowDisabled) {
		::DeleteEnhMetaFile(m_hEndArrowDisabled);
	}
	while(!m_TabList.IsEmpty()) {
		CString* pItem = m_TabList.RemoveTail();
		_delete(pItem);
	}
	while(!m_TabTipList.IsEmpty()) {
		CString* pItem = m_TabTipList.RemoveTail();
		_delete(pItem);
	}

	// fix potential resource leak - KStowell - 10-21-99
	m_Font.DeleteObject();
	m_BoldFont.DeleteObject();
}

void CCJFlatTabCtrl::CreateTabFont(const int iTabHeight)
{
	int iHeight = -(iTabHeight - 6);

	m_Font.DeleteObject();
	m_Font.CreateFont(iHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
		OUT_STROKE_PRECIS, CLIP_STROKE_PRECIS, PROOF_QUALITY, FF_SWISS, NULL);

	m_BoldFont.DeleteObject();
	m_BoldFont.CreateFont(iHeight, 0, 0, 0, FW_EXTRABOLD, FALSE, FALSE, FALSE, ANSI_CHARSET,
		OUT_STROKE_PRECIS, CLIP_STROKE_PRECIS, PROOF_QUALITY, FF_SWISS, NULL);
}

int CCJFlatTabCtrl::GetTabWidth(const int nItem) const
{
	CDC dc;
	dc.CreateCompatibleDC(NULL);
	CFont* pOldFont = dc.SelectObject((CFont*)&m_BoldFont);
	CSize size(dc.GetTextExtent(*m_TabList.GetAt(m_TabList.FindIndex(nItem))));
	
	// fix potential resource leak - KStowell - 10-20-99.
	dc.SelectObject(pOldFont);
	dc.DeleteDC();
	
	return size.cx + m_iClientHeight + (m_iClientHeight / 2);
}

int CCJFlatTabCtrl::GetTotalTabWidth(void) const
{
	int iWidth = 0;
	for (int i = 0; i < m_TabList.GetCount(); i++)
	{
		iWidth += GetTabWidth(i);
#if 0
		if (i != m_TabList.GetCount())
			iWidth -= (m_iClientHeight / 2);
#else
		// Andy Metcalfe 28/10/99
		// Fix for painting bug reported in the CodeJock Message Board Developers Forum
		// in the posting "[Bug] CCJFlatTabCtrl is not perfectly painted"
		// by "sbkang" on 4/10/99
		//
		//
		// This fix was posted by "uniken" on 27/10/99
		if (i != m_TabList.GetCount() - 1)
			iWidth -= (m_iClientHeight / 2);
#endif
	}
	return iWidth + 2;
}


int CCJFlatTabCtrl::GetTotalArrowWidth(void) const
{
	int iWidth = 1;
	if (m_bHasArrows)
		iWidth += 32;
	if (m_bHasHomeEnd)
		iWidth += 32;
	return iWidth;
}

void CCJFlatTabCtrl::InvalidateTabs(void)
{
	if (GetSafeHwnd())
	{
		// invalidate the visible tab area
		// to minimise flicker - don't erase the background
		CRect rcTabs;

		rcTabs.left = GetTotalArrowWidth();
		rcTabs.top = 0;
		rcTabs.right = rcTabs.left + (m_iTotalTabWidth - m_iXOffset);
		rcTabs.bottom = m_iClientHeight;
		InvalidateRect(&rcTabs, false);
		// invalidate the blank area to the right of the tabs
		if (rcTabs.right < m_iClientWidth)
		{
			rcTabs.left = rcTabs.right;
			rcTabs.right = m_iClientWidth;
			InvalidateRect(&rcTabs, true);
		}
	}
}

void CCJFlatTabCtrl::EnableButtons(void)
{
	if (m_bHasArrows)  //TCC: ADDED to handle situations where ther are no arrows
	{
		m_pLeftButton->EnableWindow(m_iXOffset);
		if (m_iClientWidth + m_iXOffset < m_iTotalTabWidth + GetTotalArrowWidth() + 1)
			m_pRightButton->EnableWindow(true);
		else
			m_pRightButton->EnableWindow(false);
		if (m_bHasHomeEnd)
		{
			m_pHomeButton->EnableWindow(m_iXOffset);
			m_pEndButton->EnableWindow(m_pRightButton->IsWindowEnabled());
		}
	}
}

int CCJFlatTabCtrl::DrawTab(CDC &dc, const int x, const int y, const int iHeight, bool bSelected, bool bBottom, LPCTSTR lpszTab) const
{
	CFont* pOldFont = dc.SelectObject((CFont*)&m_BoldFont);
	CSize size(dc.GetTextExtent(lpszTab));
	CRect rcText;
	rcText.left = iHeight + x - 4;
	rcText.top = 2 + y;
	rcText.right = size.cx + iHeight + x - 4;
	rcText.bottom = size.cy + 2 + y;
	
	int iHalf = iHeight / 2;
	int iWidth = iHeight + iHalf;
	
	COLORREF crBack;
	COLORREF crFore;
	if (bSelected) {
		crBack = GetSysColor(COLOR_WINDOW);
		crFore = GetSysColor(COLOR_WINDOWTEXT);
	} else {
		crBack = GetSysColor(COLOR_3DFACE);
		crFore = GetSysColor(COLOR_BTNTEXT);
	}

	CPen penOutline(PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT));
	CPen penShadow(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW));
	CBrush brush;
	brush.CreateSolidBrush(crBack);
	
	POINT points[4];
	points[0].x = x;                     points[0].y = iHeight + y - 1;
	points[1].x = iHalf + x;             points[1].y = y;
	points[2].x = size.cx + iHeight + x; points[2].y = y;
	points[3].x = size.cx + iWidth + x;  points[3].y = iHeight + y - 1;
	
	if (bBottom) {
		// swap vertical coordinates
		points[0].y = points[1].y;
		points[2].y = points[3].y;
		points[1].y = points[2].y;
		points[3].y = points[0].y;
	}

	CPen* pOldPen = dc.SelectObject(&penOutline);
	dc.SetBkColor(crBack);
	CBrush* pOldBrush = dc.SelectObject(&brush);
	dc.Polygon(points, 4);
	dc.SelectObject(bSelected ? (CFont*)&m_BoldFont : (CFont*)&m_Font);
	dc.SetTextColor(crFore);
	dc.DrawText(lpszTab, rcText, DT_CENTER);
	dc.SelectObject(&penShadow);
	
	if (bSelected) {
		dc.MoveTo(iHalf + x + 1, points[1].y);
		dc.LineTo(size.cx + iHeight + x, points[1].y);
		CPen penBack(PS_SOLID, 1, crBack);
		dc.SelectObject(&penBack);
		dc.MoveTo(x + 1, points[0].y);
		dc.LineTo(size.cx + iWidth + x, points[0].y);
	} else {
		dc.MoveTo(iHalf + x + 1, points[1].y);
		dc.LineTo(size.cx + iHeight + x - 1, points[1].y);
		dc.LineTo(size.cx + iWidth + x - 1, points[0].y);
	}

	// fix potential resource leak - KStowell - 10-21-99
	dc.SelectObject(pOldFont);
	dc.SelectObject(pOldPen);
	dc.SelectObject(pOldBrush);
	penOutline.DeleteObject();
	penShadow.DeleteObject();
	brush.DeleteObject();

	return size.cx + iWidth;
}

BOOL CCJFlatTabCtrl::InsertItem(const int nItem, LPCTSTR lpszItem)
{
	if (nItem < 0 || nItem > m_TabList.GetCount())
		return -1;
	
	// add tab to our list
	CString* pItem = new CString;
	ASSERT(pItem);

	*pItem = lpszItem;
	m_TabList.AddTail(pItem);
	
    // PGB - Add the tabs tip text to the list

	CString* pTipItem = new CString;
	ASSERT(pTipItem);

	*pTipItem = lpszItem;
	m_TabTipList.AddTail(pTipItem);

    //

	if (m_TabList.GetCount() == 1)
		m_iCurSel = 0;

   	m_iTotalTabWidth = GetTotalTabWidth();      // PGB - Recalculate the total tab width now we have added a new item
	
    EnableButtons();
	InvalidateTabs();

	return nItem;
}

BOOL CCJFlatTabCtrl::DeleteItem(int nItem)
{
	if (nItem < 0 || nItem >= m_TabList.GetCount())
		return FALSE;
	
	POSITION pos = m_TabList.FindIndex(nItem);
	ASSERT(pos);
	
	CString* pItem = m_TabList.GetAt(pos);
	m_TabList.RemoveAt(pos);
	_delete(pItem);

    // PGB - Stole the following code from the OnLButtonDown() function
    // in order to cause the tab control to show the newly selected item
    // after deleting an item from the list

	// warn parent that the selection is about to change
	int id = GetDlgCtrlID();
	NMHDR hdr;
	hdr.hwndFrom = m_hWnd;
	hdr.idFrom = id;
	hdr.code = TCN_SELCHANGING;
	if (GetParent()->SendMessage(WM_NOTIFY, id, (LPARAM)&hdr) == 0)
	{
	    if (m_iCurSel >= m_TabList.GetCount())
        {
            SetCurSel(m_TabList.GetCount() - 1); // PGB - Reset the currently selected tab to the last one in the list
			InvalidateTabs();
        }

		// notify parent that the selection has changed
		hdr.hwndFrom = m_hWnd;
		hdr.idFrom = id;
		hdr.code = TCN_SELCHANGE;
		GetParent()->SendMessage(WM_NOTIFY, id, (LPARAM)&hdr);
	}

   	m_iTotalTabWidth = GetTotalTabWidth();      // PGB - Recalculate the total tab width now we have deleted an item

    EnableButtons();
	InvalidateTabs();

	return TRUE;
}

BOOL CCJFlatTabCtrl::DeleteAllItems()
{
	while(!m_TabList.IsEmpty()) {
		CString* pItem = m_TabList.RemoveTail();
		_delete(pItem);
	}

	while(!m_TabTipList.IsEmpty()) {
		CString* pItem = m_TabTipList.RemoveTail();
		_delete(pItem);
	}

	m_iCurSel = -1;                             // PGB - Reset the currently selected tab to -1 as we have no tabs in our list now.

   	m_iTotalTabWidth = GetTotalTabWidth();      // PGB - Recalculate the total tab width now we have deleted all items

    EnableButtons();
	InvalidateTabs();

	return TRUE;
}

BOOL CCJFlatTabCtrl::GetItemRect(int nItem, LPRECT lpRect)
{
	if (nItem < 0 || nItem >= m_TabList.GetCount())
		return FALSE;
	
	int x = GetTotalArrowWidth();
	for (int i = 0; i < nItem; i++)
	{
		x += GetTabWidth(i);
		if (i != m_TabList.GetCount())
			x -= (m_iClientHeight / 2) + 2;
	}
	lpRect->left = x - m_iXOffset;
	lpRect->top = 0;
	lpRect->right = lpRect->left + GetTabWidth(nItem);
	lpRect->bottom = m_iClientHeight;
	return TRUE;
}

int CCJFlatTabCtrl::HitTest(TCHITTESTINFO *pHitTestInfo) const
{
	// ignore hits on the buttons
	int iHitX = pHitTestInfo->pt.x;
	if (iHitX < GetTotalArrowWidth())
		return -1;
	
	// check if any tabs were hit
	int x = GetTotalArrowWidth() - m_iXOffset;
	for (int i = 0; i < m_TabList.GetCount(); i++)
	{
		int iTabWidth = GetTabWidth(i);
		if (i != m_TabList.GetCount())
			iTabWidth -= (m_iClientHeight / 2) + 2;
		if ((x <= iHitX) && (iHitX <= x + iTabWidth))
			return i;
		x += iTabWidth;
	}
	
	// hit point is right of rightmost tab
	return -1;
}

int CCJFlatTabCtrl::SetCurSel(int nItem)
{
	if (nItem < 0 || nItem >= m_TabList.GetCount())
		return -1;
	
	int iPrevSel = m_iCurSel;
	m_iCurSel = nItem;
	
	// test if we need to center on the selected tab
	CRect rcItem;
	if (GetItemRect(nItem, &rcItem))
	{
		// test if the tab is off on the left
		int iTotalArrowWidth = GetTotalArrowWidth();
		rcItem.left -= iTotalArrowWidth;
		if (rcItem.left < 0)
			m_iXOffset += rcItem.left;
		else
		{
			// test if the tab is off on the right
			rcItem.right -= iTotalArrowWidth;
			int iTabAreaWidth = m_iClientWidth - iTotalArrowWidth;
			if (rcItem.right > iTabAreaWidth)
				m_iXOffset += (rcItem.right - iTabAreaWidth);
		}
	}
	
	EnableButtons();
	InvalidateTabs();

	return iPrevSel;
}

BEGIN_MESSAGE_MAP(CCJFlatTabCtrl, CWnd)
	//{{AFX_MSG_MAP(CCJFlatTabCtrl)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_SIZE()
	ON_WM_CREATE()
	ON_BN_CLICKED(IDC_LEFTBUTTON, OnLeftArrow)
	ON_BN_CLICKED(IDC_RIGHTBUTTON, OnRightArrow)
	ON_BN_CLICKED(IDC_HOMEBUTTON, OnHomeArrow)
	ON_BN_CLICKED(IDC_ENDBUTTON, OnEndArrow)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCJFlatTabCtrl message handlers

BOOL CCJFlatTabCtrl::PreCreateWindow(CREATESTRUCT& cs) 
{
	if (!CWnd::PreCreateWindow(cs))
		return FALSE;
	
	cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS,
            ::LoadCursor(NULL, IDC_ARROW), NULL, NULL);
	
	return TRUE;
}

BOOL CCJFlatTabCtrl::PreTranslateMessage(MSG* pMsg)
{
	ASSERT_VALID(this);

    if (::IsWindow(m_ToolTip.m_hWnd))
                                                                                                                                                                                                                                                                                                                                                                                                                           {
	    if(pMsg->message == WM_MOUSEMOVE && pMsg->hwnd == m_hWnd)
        {
		    CPoint Point(LOWORD(pMsg->lParam), HIWORD(pMsg->lParam));

	        TCHITTESTINFO test;
	        test.pt = Point;
	        int iTab = HitTest(&test);

            if (iTab >= 0)
            {
                CString strText;

                strText = (LPCTSTR) *m_TabTipList.GetAt(m_TabTipList.FindIndex(iTab));

                TRACE2("Mouse over tab %d : %s\n", iTab, strText);

			    m_ToolTip.UpdateTipText(strText, this);
			    m_ToolTip.RelayEvent(pMsg);
            } else  {
				// VC5 compatibility - Kirk Stowell - 10-21-99
				::SendMessage(m_ToolTip.m_hWnd, TTM_POP, 0, 0L);
            }
        }
        else {
			// VC5 compatibility - Kirk Stowell - 10-21-99
			::SendMessage(m_ToolTip.m_hWnd, TTM_POP, 0, 0L);
        }
    }

	return CWnd::PreTranslateMessage(pMsg);
}

void CCJFlatTabCtrl::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	CBrush brush;
	brush.CreateSysColorBrush(COLOR_3DFACE);
	CPen penBlack(PS_SOLID, 1, RGB(0,0,0));
	
	// draw the black line along the left of the control
	CPen* pOldPen = dc.SelectObject(&penBlack);
	dc.MoveTo(0, 0);
	dc.LineTo(0, m_iClientHeight);
	
	// draw the black line along the top/bottom of the control
	int iTotalArrowWidth = GetTotalArrowWidth();
	int iEndOfTab = iTotalArrowWidth + m_iTotalTabWidth - m_iXOffset;
	dc.MoveTo(iEndOfTab, m_bTabsOnBottom ? 0 : m_iClientHeight - 1);
	dc.LineTo(m_iClientWidth, m_bTabsOnBottom ? 0 : m_iClientHeight - 1);
	
	// fill the empty background area
	int iFillHeight = m_iClientHeight - (m_bTabsOnBottom ? 0 : 1);
	CRect rcBack(iEndOfTab, m_bTabsOnBottom ? 1 : 0, m_iClientWidth, iFillHeight);
	dc.FillRect(rcBack, &brush);
	
	// create a bitmap of all the tabs
	CDC dcMem;
	dcMem.CreateCompatibleDC(NULL);
	CBitmap bitmap;
	bitmap.CreateCompatibleBitmap(&dc, m_iTotalTabWidth, m_iClientHeight);
	CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
	CBrush* pOldBrush = dcMem.SelectObject(&brush);
	CRect rc(0, m_bTabsOnBottom ? 1 : 0, m_iTotalTabWidth, iFillHeight);
	dcMem.FillRect(rc, &brush);
	
	int iOverlap = (m_iClientHeight / 2) + 2;
	int x = 0;
	int iSelX = 0;
	for (int i = 0; i < m_TabList.GetCount(); i++)
	{
		if (i != m_iCurSel)
			x += DrawTab(dcMem, x, 0, m_iClientHeight, false, m_bTabsOnBottom, *m_TabList.GetAt(m_TabList.FindIndex(i))) - iOverlap;
		else
		{
			iSelX = x;
			x += GetTabWidth(i) - iOverlap;
		}
	}
    if (m_iCurSel >= 0)
    	DrawTab(dcMem, iSelX, 0, m_iClientHeight, true, m_bTabsOnBottom, *m_TabList.GetAt(m_TabList.FindIndex(m_iCurSel)));
	
	// blit the bitmap onto the control
	dc.BitBlt(iTotalArrowWidth, 0, m_iClientWidth - iTotalArrowWidth, m_iClientHeight, &dcMem, m_iXOffset, 0, SRCCOPY);

	// fix potential resource leak - KStowell - 10-21-99.
	dcMem.SelectObject(pOldPen);
	dcMem.SelectObject(pOldBitmap);
	dcMem.SelectObject(pOldBrush);
	dcMem.DeleteDC();
	brush.DeleteObject();
	penBlack.DeleteObject();
	bitmap.DeleteObject();
}

void CCJFlatTabCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	TCHITTESTINFO test;
	test.pt = point;
	int iTab = HitTest(&test);
	if ((iTab != -1) && (iTab != m_iCurSel))
	{
		// warn parent that the selection is about to change
		int id = GetDlgCtrlID();
		NMHDR hdr;
		hdr.hwndFrom = m_hWnd;
		hdr.idFrom = id;
		hdr.code = TCN_SELCHANGING;
		if (GetParent()->SendMessage(WM_NOTIFY, id, (LPARAM)&hdr) == 0)
		{
			// parent has given permission for the selection to change
			SetCurSel(iTab);
			InvalidateTabs();
			
			// notify parent that the selection has changed
			hdr.hwndFrom = m_hWnd;
			hdr.idFrom = id;
			hdr.code = TCN_SELCHANGE;
			GetParent()->SendMessage(WM_NOTIFY, id, (LPARAM)&hdr);
		}
	}
	
	CWnd::OnLButtonDown(nFlags, point);
}

void CCJFlatTabCtrl::OnSize(UINT nType, int cx, int cy) 
{
	CWnd::OnSize(nType, cx, cy);
	
	// TODO: Add your message handler code here
	if (m_bHasHomeEnd) {
		m_pHomeButton->MoveWindow(1, 0, 16, cy);
		m_pLeftButton->MoveWindow(17, 0, 16, cy);
		m_pRightButton->MoveWindow(33, 0, 16, cy);
		m_pEndButton->MoveWindow(49, 0, 16, cy);
	} else {
		if (m_bHasArrows) { //TCC: ADDED to handle situations where ther are no arrows
			m_pLeftButton->MoveWindow(1, 0, 16, cy);
			m_pRightButton->MoveWindow(17, 0, 16, cy);
		}
	}
	
	m_iClientWidth  = cx;
	m_iClientHeight = cy;

	CreateTabFont(cy);
	
	m_iTotalTabWidth = GetTotalTabWidth();
	EnableButtons();
}

int CCJFlatTabCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	// TODO: Add your specialized creation code here
	if (lpCreateStruct->style & FTS_BOTTOM) {
		m_bTabsOnBottom = true;
	}

    if (lpCreateStruct->style & TCS_TOOLTIPS) {
	    m_ToolTip.Create(this);
	    m_ToolTip.Activate(TRUE);
	    m_ToolTip.AddTool(this, _T("Flat Tab Tool Tip"));
        m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, SHRT_MAX); // Allow multiline tooltips
    }

	if (lpCreateStruct->style & FTS_HASARROWS)
	{
		m_bHasArrows = true;
		POINT points[4];
		points[0].x = 100; points[0].y = 0;
		points[1].x = 0;   points[1].y = 50;
		points[2].x = 100; points[2].y = 100;
		points[3].x = 100; points[3].y = 0;

		////////////////////////////////////////////////////////////////////////////////
		// This portion of the code was re-written to help eliminate a nasty resource
		// leak. It would seem that the CMetaFileDC was not freeing the GDI resources
		// completely whenever CMetaFileDC::CloseEnhanced() was called. This may be due 
		// to a BUG in MFC or perhaps this class, in any event, this seems to resolve 
		// the issue. - KStowell - 10-23-99.
		////////////////////////////////////////////////////////////////////////////////
		HPEN   hPen			  = ::CreatePen( PS_SOLID, 1, RGB( 0,0,0 ));
		HPEN   hPenDisabled   = ::CreatePen( PS_SOLID, 1, RGB( 128,128,128 ));
		HBRUSH hBrush		  = ::CreateSolidBrush( RGB( 0,0,0 ));
		HBRUSH hBrushDisabled = ::CreateSolidBrush( RGB( 128,128,128 ));;
		
		// create left arrow metafile
		HDC hMetaDC	     = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
		HPEN hOldPen     = (HPEN)::SelectObject( hMetaDC, hPen );
		HBRUSH hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrush );
		::Polygon( hMetaDC, points, 4 );
		::SelectObject( hMetaDC, hOldPen );
		::SelectObject( hMetaDC, hOldBrush );
		m_hLeftArrow = ::CloseEnhMetaFile( hMetaDC );

		// create disabled left arrow metafile
		hMetaDC	  = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
		hOldPen   = (HPEN)::SelectObject( hMetaDC, hPenDisabled );
		hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrushDisabled );
		::Polygon( hMetaDC, points, 4 );
		::SelectObject( hMetaDC, hOldPen );
		::SelectObject( hMetaDC, hOldBrush );
		m_hLeftArrowDisabled = ::CloseEnhMetaFile( hMetaDC );

		points[0].x = 0;   points[0].y = 0;
		points[1].x = 100; points[1].y = 50;
		points[2].x = 0;   points[2].y = 100;
		points[3].x = 0;   points[3].y = 0;
		
		// create right arrow metafile
		hMetaDC	  = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
		hOldPen   = (HPEN)::SelectObject( hMetaDC, hPen );
		hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrush );
		::Polygon( hMetaDC, points, 4 );
		::SelectObject( hMetaDC, hOldPen );
		::SelectObject( hMetaDC, hOldBrush );
		m_hRightArrow = ::CloseEnhMetaFile( hMetaDC );
		
		// create disabled right arrow metafile
		hMetaDC	  = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
		hOldPen   = (HPEN)::SelectObject( hMetaDC, hPenDisabled );
		hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrushDisabled );
		::Polygon( hMetaDC, points, 4 );
		::SelectObject( hMetaDC, hOldPen );
		::SelectObject( hMetaDC, hOldBrush );
		m_hRightArrowDisabled = ::CloseEnhMetaFile( hMetaDC );
		
		CRect rcButton(0, 0, 16, 16);
		m_pLeftButton = new CCJMetaFileButton;
		m_pLeftButton->Create(NULL, WS_CHILD | WS_VISIBLE | WS_DISABLED | BS_OWNERDRAW, rcButton, this, IDC_LEFTBUTTON);
		m_pLeftButton->SetMetaFiles(m_hLeftArrow, 0, 0, m_hLeftArrowDisabled);
		
		m_pRightButton = new CCJMetaFileButton;
		m_pRightButton->Create(NULL, WS_CHILD | WS_VISIBLE | WS_DISABLED | BS_OWNERDRAW, rcButton, this, IDC_RIGHTBUTTON);
		m_pRightButton->SetMetaFiles(m_hRightArrow, 0, 0, m_hRightArrowDisabled);
		
		if (lpCreateStruct->style & FTS_HASHOMEEND)
		{
			m_bHasHomeEnd = true;

            // Setup the Home arrow button
       		points[0].x = 100;   points[0].y = 0;
		    points[1].x = 0;     points[1].y = 50;
		    points[2].x = 100;   points[2].y = 100;
		    points[3].x = 100;   points[3].y = 0;

			// create right arrow metafile
			HDC hMetaDC	     = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
			HPEN hOldPen     = (HPEN)::SelectObject( hMetaDC, hPen );
			HBRUSH hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrush );
			::Polygon( hMetaDC, points, 4 );
			::MoveToEx( hMetaDC, 0, 0, NULL );
			::LineTo( hMetaDC, 0, 100 );
			::SelectObject( hMetaDC, hOldPen );
			::SelectObject( hMetaDC, hOldBrush );
			m_hHomeArrow = ::CloseEnhMetaFile( hMetaDC );
		    
			// create disabled right arrow metafile
			hMetaDC	  = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
			hOldPen   = (HPEN)::SelectObject( hMetaDC, hPenDisabled );
			hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrushDisabled );
			::Polygon( hMetaDC, points, 4 );
			::MoveToEx( hMetaDC, 0, 0, NULL );
			::LineTo( hMetaDC, 0, 100 );
			::SelectObject( hMetaDC, hOldPen );
			::SelectObject( hMetaDC, hOldBrush );
			m_hHomeArrowDisabled = ::CloseEnhMetaFile( hMetaDC );

			m_pHomeButton = new CCJMetaFileButton;
			m_pHomeButton->Create(NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, rcButton, this, IDC_HOMEBUTTON);
			m_pHomeButton->SetMetaFiles(m_hHomeArrow, 0, 0, m_hHomeArrowDisabled);

            // Setup the End arrow button
       		points[0].x = 0;   points[0].y = 0;
		    points[1].x = 100; points[1].y = 50;
		    points[2].x = 0;   points[2].y = 100;
		    points[3].x = 0;   points[3].y = 0;

			// create right arrow metafile
			hMetaDC	  = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
			hOldPen   = (HPEN)::SelectObject( hMetaDC, hPen );
			hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrush );
			::Polygon( hMetaDC, points, 4 );
			::MoveToEx( hMetaDC, 100, 0, NULL );
			::LineTo( hMetaDC, 100, 100 );
			::SelectObject( hMetaDC, hOldPen );
			::SelectObject( hMetaDC, hOldBrush );
			m_hEndArrow = ::CloseEnhMetaFile( hMetaDC );
		    
			// create disabled right arrow metafile
			hMetaDC	  = ::CreateEnhMetaFile( NULL, NULL, NULL, NULL );
			hOldPen   = (HPEN)::SelectObject( hMetaDC, hPenDisabled );
			hOldBrush = (HBRUSH)::SelectObject( hMetaDC, hBrushDisabled );
			::Polygon( hMetaDC, points, 4 );
			::MoveToEx( hMetaDC, 100, 0, NULL );
			::LineTo( hMetaDC, 100, 100 );
			::SelectObject( hMetaDC, hOldPen );
			::SelectObject( hMetaDC, hOldBrush );
			m_hEndArrowDisabled = ::CloseEnhMetaFile( hMetaDC );

			m_pEndButton = new CCJMetaFileButton;
			m_pEndButton->Create(NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, rcButton, this, IDC_ENDBUTTON);
			m_pEndButton->SetMetaFiles(m_hEndArrow, 0, 0, m_hEndArrowDisabled);
			
			m_pHomeButton->MoveWindow(1, 0, 16, 16);
			m_pLeftButton->MoveWindow(17, 0, 16, 16);
			m_pRightButton->MoveWindow(33, 0, 16, 16);
			m_pEndButton->MoveWindow(49, 0, 16, 16);

		} else {
			m_pLeftButton->MoveWindow(1, 0, 16, 16);
			m_pRightButton->MoveWindow(17, 0, 16, 16);
		}

		// fix potential resource leak - KStowell - 10-23-99
		::DeleteObject(hPen);
		::DeleteObject(hPenDisabled);
		::DeleteObject(hBrush);
		::DeleteObject(hBrushDisabled);
	}

	return 0;
}

void CCJFlatTabCtrl::OnLeftArrow() 
{
    // Move the tabs right, ensuring that we move right by one
    // whole tab each time ala Microsoft Access

	CPoint Point(GetTotalArrowWidth()+1, 1);

	TCHITTESTINFO test;
	test.pt = Point;
	int iTab = HitTest(&test);

    if (iTab != -1)
    {
        m_iXOffset = 0;
        for (int iLoop = 0; iLoop <= iTab - 1; iLoop++)
        {
            RECT rect;
            if (GetItemRect(iLoop, &rect))
            {
                m_iXOffset += rect.left;
            }
        }
        m_iXOffset -= GetTotalArrowWidth();

	    EnableButtons();
	    InvalidateTabs();
    }
}

void CCJFlatTabCtrl::OnRightArrow() 
{
    // Move the tabs left, ensuring that we move left by one
    // whole tab each time ala Microsoft Access

	CPoint Point(GetTotalArrowWidth()+1, 1);

	TCHITTESTINFO test;
	test.pt = Point;
	int iTab = HitTest(&test);

    if (iTab != -1)
    {
        m_iXOffset = 0;
        for (int iLoop = 0; iLoop <= iTab + 1; iLoop++)
        {
            RECT rect;
            if (GetItemRect(iLoop, &rect))
            {
                m_iXOffset += rect.left;
            }
        }
        m_iXOffset -= GetTotalArrowWidth();

	    EnableButtons();
	    InvalidateTabs();
    }
}

void CCJFlatTabCtrl::OnHomeArrow() 
{
	// TODO: Add your control notification handler code here
	m_iXOffset = 0;
	EnableButtons();
	InvalidateTabs();
}

void CCJFlatTabCtrl::OnEndArrow() 
{
	// TODO: Add your control notification handler code here
	m_iXOffset = m_iTotalTabWidth + GetTotalArrowWidth() - m_iClientWidth + 1;
	EnableButtons();
	InvalidateTabs();
}

void CCJFlatTabCtrl::SetTipText(int nItem, LPCTSTR lpszTabTip)
{
    if (nItem < m_TabList.GetCount())
    {
	    for (int i = 0; i < m_TabList.GetCount(); i++)
	    {
            if (i == nItem)
            {
                CString *pTipItem = m_TabTipList.GetAt(m_TabTipList.FindIndex(nItem));

            	*pTipItem = lpszTabTip;

                break;
            }
	    }
    }

    return;
}

CString CCJFlatTabCtrl::GetTipText(int nItem)
{
    CString strText;

    if (nItem < m_TabList.GetCount())
    {
	    for (int i = 0; i < m_TabList.GetCount(); i++)
	    {
            if (i == nItem)
            {
                strText = *m_TabTipList.GetAt(m_TabTipList.FindIndex(nItem));
                break;
            }
	    }
    }

    return strText;
}

void CCJFlatTabCtrl::Home()
{
    OnHomeArrow();
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Riverblade Limited
United Kingdom United Kingdom
I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
    * Also known as the Standard Army Bootswitch. But that's another story...
Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

Comments and Discussions