////////////////////////////////////////////////////////////////
// CTabCtrlWithDisable 1998 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// CTabCtrlWithDisable implements a CTabCtrl with tabs that you can disable.

#include "StdAfx.h"
#include "DisabTab.h"

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

IMPLEMENT_DYNAMIC(CTabCtrlWithDisable, CTabCtrl)

BEGIN_MESSAGE_MAP(CTabCtrlWithDisable, CTabCtrl)
	//{{AFX_MSG_MAP(CTabCtrlWithDisable)
	//}}AFX_MSG_MAP
	ON_NOTIFY_REFLECT_EX(TCN_SELCHANGING, OnSelChanging)
END_MESSAGE_MAP()

CTabCtrlWithDisable::CTabCtrlWithDisable()
{
	m_bPrintOnly = FALSE;
}

CTabCtrlWithDisable::~CTabCtrlWithDisable()
{
}

// Subclass the tab control: also make ownder-draw
CTabCtrlWithDisable::SubclassDlgItem(UINT nID, CWnd* pParent)
{
	if (!CTabCtrl::SubclassDlgItem(nID, pParent))
		return FALSE;

	ModifyStyle(0, TCS_OWNERDRAWFIXED);

	// If first tab is disabled, go to next enabled tab
	if (!IsTabEnabled(0))
	{
		int iTab = NextEnabledTab(0, TRUE);
		SetActiveTab(iTab);
	}
	return TRUE;
}

// Draw the tab: mimic SysTabControl32, except use gray if tab is disabled
void CTabCtrlWithDisable::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	DRAWITEMSTRUCT& ds = *lpDrawItemStruct;
	
	int iItem = ds.itemID;

	// Get tab item info
	char text[128];
	TCITEM tci;
	tci.mask = TCIF_TEXT;
	tci.pszText = text;
	tci.cchTextMax = sizeof(text);
	GetItem(iItem, &tci);

	// use draw item DC
	CDC dc;
	dc.Attach(ds.hDC);

	dc.FillSolidRect(&ds.rcItem, GetSysColor(COLOR_3DFACE));

	// calculate text rectangle and color
	CRect rc = ds.rcItem;
	rc += CPoint(0,3);	// ?? by trial and error

	// draw the text
	OnDrawText(dc, rc, text, !IsTabEnabled(iItem));

	dc.Detach();
}

// Draw tab text. You can override to use different color/font.
void CTabCtrlWithDisable::OnDrawText(CDC& dc, CRect rc, CString sText, BOOL bDisabled)
{
	if (bDisabled)
		rc += CPoint(1,1);
    dc.SetBkMode(TRANSPARENT);
	dc.SetTextColor(GetSysColor(bDisabled ? COLOR_3DHILIGHT : COLOR_BTNTEXT));
	dc.DrawText(sText, &rc, DT_CENTER|DT_VCENTER);

	if (bDisabled)
	{
		// disabled: draw again shifted northwest for shadow effect
		rc -= CPoint(1,1);
		dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT));
		dc.DrawText(sText, &rc, DT_CENTER|DT_VCENTER);
	}
}

// Selection is changing: disallow if tab is disabled
BOOL CTabCtrlWithDisable::OnSelChanging(NMHDR* pnmh, LRESULT* pRes)
{
	// Figure out index of new tab we are about to go to, as opposed
	// to the current one we're at. Believe it or not, Windows doesn't
	// pass this info
	TC_HITTESTINFO htinfo;
	GetCursorPos(&htinfo.pt);
	ScreenToClient(&htinfo.pt);
	int iNewTab = HitTest(&htinfo);

	BOOL bDisallowChange = (iNewTab >= 0 && !IsTabEnabled(iNewTab));
	*pRes = bDisallowChange;

	// If change disallowed, return TRUE and stop processing; otherwise
	// (change allowed) return FALSE to let MFC continue routing the message,
	// so Windows will send PSN_KILLACTIVE to de-activate current prop page.
	return bDisallowChange;
}

// Trap arrow-left key to skip disabled tabs.
// This is the only way to know where we're coming from--ie from
// arrow-left (prev) or arrow-right (next).
BOOL CTabCtrlWithDisable::PreTranslateMessage(MSG* pMsg)
{
	if (pMsg->message == WM_KEYDOWN &&
		(pMsg->wParam == VK_LEFT || pMsg->wParam == VK_RIGHT))
	{
		int iNewTab = (pMsg->wParam == VK_LEFT) ?
			PrevEnabledTab(GetCurSel(), FALSE) :
			NextEnabledTab(GetCurSel(), FALSE);
		if (iNewTab >= 0)
			SetActiveTab(iNewTab);
		return TRUE;
	}
	return CTabCtrl::PreTranslateMessage(pMsg);
}

// Translate parent property sheet message. Translates Control-Tab and
// Control-Shift-Tab keys. These are normally handled by the property
// sheet, so you must call this function from your prop sheet's
// PreTranslateMessage function.
BOOL CTabCtrlWithDisable::TranslatePropSheetMsg(MSG* pMsg)
{
	WPARAM key = pMsg->wParam;
	if (pMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_CONTROL) < 0 &&
		(key == VK_TAB || key == VK_PRIOR || key == VK_NEXT))
	{
		int iNewTab = (key==VK_PRIOR || GetAsyncKeyState(VK_SHIFT) < 0) ?
			PrevEnabledTab(GetCurSel(), TRUE) :
			NextEnabledTab(GetCurSel(), TRUE);
		if (iNewTab >= 0)
			SetActiveTab(iNewTab);
		return TRUE;
	}
	return FALSE;
}

// Helper to set the active page, when moving backwards (left-arrow and
// Control-Shift-Tab). Must simulate Windows messages to tell parent I
// am changing the tab; SetCurSel does not do this!!
//
// In normal operation, this fn will always succeed, because I don't call it
// unless I already know IsTabEnabled() = TRUE; but if you call SetActiveTab
// with a random value, it could fail.
BOOL CTabCtrlWithDisable::SetActiveTab(UINT iNewTab)
{
	// send the parent TCN_SELCHANGING
	NMHDR nmh;
	nmh.hwndFrom = m_hWnd;
	nmh.idFrom = GetDlgCtrlID();
	nmh.code = TCN_SELCHANGING;

	if (GetParent()->SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh) >=0)
	{
		// OK to change: set the new tab
		SetCurSel(iNewTab);

		// send parent TCN_SELCHANGE
		nmh.code = TCN_SELCHANGE;
		GetParent()->SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
		return TRUE;
	}
	return FALSE;
}

// Return the index of the next enabled tab after a given index, or -1 if none
// (0 = first tab).
// If bWrap is TRUE, wrap from beginning to end; otherwise stop at zero.
int CTabCtrlWithDisable::NextEnabledTab(int iCurrentTab, BOOL bWrap)
{
	int nTabs = GetItemCount();
	for (int iTab = iCurrentTab+1; iTab != iCurrentTab; iTab++)
	{
		if (iTab >= nTabs)
		{
			if (!bWrap)
				return -1;
			iTab = 0;
		}
	
		if (IsTabEnabled(iTab))
			return iTab;
	}
	return -1;
}

// Return the index of the previous enabled tab before a given index, or -1.
// (0 = first tab).
// If bWrap is TRUE, wrap from beginning to end; otherwise stop at zero.
int CTabCtrlWithDisable::PrevEnabledTab(int iCurrentTab, BOOL bWrap)
{
	for (int iTab = iCurrentTab-1; iTab != iCurrentTab; iTab--)
	{
		if (iTab < 0)
		{
			if (!bWrap)
				return -1;
			iTab = GetItemCount() - 1;
		}

		if (IsTabEnabled(iTab))
			return iTab;
	}
	return -1;
}

BOOL CTabCtrlWithDisable::IsTabEnabled(int iTab)
{
	if (m_bPrintOnly && (iTab != 4))
		return FALSE;

	return TRUE;
}
