summaryrefslogtreecommitdiff
path: root/win/disabtab.cpp
blob: 69e5a74a65abc97cf0197cfdc347259f83f9a2ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
////////////////////////////////////////////////////////////////
// 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
BOOL 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;
}