summaryrefslogtreecommitdiff
path: root/new/tab_windows.c
blob: 00fccb00b1c182d98520d01306c0662baa079242 (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
// 12 april 2015
#include "uipriv_windows.h"

// TODO
// - tab change notifications aren't being sent on wine (anymore...? TODO)
// - tell wine developers that tab controls do respond to parent changes on real windows (at least comctl6 tab controls do)

struct tab {
	uiParent **pages;
	uintmax_t len;
	uintmax_t cap;
};

static BOOL onWM_COMMAND(uiControl *c, WORD code, LRESULT *lResult)
{
	return FALSE;
}

// we have to handle hiding and showing of tab pages ourselves
static BOOL onWM_NOTIFY(uiControl *c, NMHDR *nm, LRESULT *lResult)
{
	struct tab *t = (struct tab *) (c->data);
	LRESULT n;

	switch (nm->code) {
	case TCN_SELCHANGING:
		n = SendMessageW(uiControlHWND(c), TCM_GETCURSEL, 0, 0);
		if (n != (LRESULT) (-1))		// if we're changing to a real tab
			ShowWindow(uiParentHWND(t->pages[n]), SW_HIDE);
		*lResult = FALSE;			// and allow the change
		return TRUE;
	case TCN_SELCHANGE:
		n = SendMessageW(uiControlHWND(c), TCM_GETCURSEL, 0, 0);
		if (n != (LRESULT) (-1)) {		// if we're changing to a real tab
			ShowWindow(uiParentHWND(t->pages[n]), SW_SHOW);
			// because we only resize the current child on resize, we'll need to trigger an update here
			// don't call uiParentUpdate(); doing that won't size the content area (so we'll still have a 0x0 content area, for instance)
			SendMessageW(uiControlHWND(c), msgUpdateChild, 0, 0);
		}
		*lResult = 0;
		return TRUE;
	}
	return FALSE;
}

static void onWM_DESTROY(uiControl *c)
{
	struct tab *t = (struct tab *) (c->data);

	// no need to worry about freeing the pages themselves; they'll destroy themselves after we return
	uiFree(t->pages);
	uiFree(t);
}

static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height)
{
	// TODO
}

// common code for resizes
static void resizeTab(uiControl *c, LONG width, LONG height)
{
	struct tab *t = (struct tab *) (c->data);
	HWND hwnd;
	LRESULT n;
	RECT r;

	hwnd = uiControlHWND(c);

	n = SendMessageW(hwnd, TCM_GETCURSEL, 0, 0);
	if (n == (LRESULT) (-1))		// no child selected; do nothing
		return;

	// make a rect at (0, 0) of the given window size
	// this should give us the correct client coordinates
	r.left = 0;
	r.top = 0;
	r.right = width;
	r.bottom = height;
	// convert to the display rectangle
	SendMessageW(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM) (&r));

	if (MoveWindow(uiParentHWND(t->pages[n]), r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE) == 0)
		logLastError("error resizing current tab page in resizeTab()");
}

// and finally, because we have to resize parents, we have to handle resizes and updates
static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
	uiControl *c = (uiControl *) dwRefData;
	WINDOWPOS *wp = (WINDOWPOS *) lParam;
	LRESULT lResult;
	RECT r;

	switch (uMsg) {
	case WM_WINDOWPOSCHANGED:
		if ((wp->flags & SWP_NOSIZE) != 0)
			break;
		// first, let the tab control handle it
		lResult = (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
		// we have the window rect width as part of the WINDOWPOS; resize
		resizeTab(c, wp->cx, wp->cy);
		return lResult;
	case msgUpdateChild:
		if (GetWindowRect(uiControlHWND(c), &r) == 0)
			logLastError("error getting Tab window rect for synthesized resize message in tabSubProc()");
		// these are in screen coordinates, which match what WM_WINDOWPOSCHANGED gave us (thanks TODOTODOTODOTODOTODOTODOTODO)
		resizeTab(c, r.right - r.left, r.bottom - r.top);
		return 0;
	case WM_NCDESTROY:
		if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, uIdSubclass) == FALSE)
			logLastError("error removing Tab resize handling subclass in tabSubProc()");
		break;
	}
	return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
}

uiControl *uiNewTab(void)
{
	uiControl *c;
	struct tab *t;
	uiWindowsNewControlParams p;
	HWND hwnd;

	p.dwExStyle = 0;		// don't set WS_EX_CONTROLPARENT yet; we do that dynamically in the message loop (see main_windows.c)
	p.lpClassName = WC_TABCONTROLW;
	p.lpWindowName = L"";
	p.dwStyle = TCS_TOOLTIPS | WS_TABSTOP;
	p.hInstance = hInstance;
	p.useStandardControlFont = TRUE;
	p.onWM_COMMAND = onWM_COMMAND;
	p.onWM_NOTIFY = onWM_NOTIFY;
	p.onWM_DESTROY = onWM_DESTROY;
	c = uiWindowsNewControl(&p);

	c->preferredSize = preferredSize;

	t = uiNew(struct tab);
	c->data = t;

	hwnd = uiControlHWND(c);
	if ((*fv_SetWindowSubclass)(hwnd, tabSubProc, 0, (DWORD_PTR) c) == FALSE)
		logLastError("error subclassing Tab to give it its own resize handler in uiNewTab()");

	return c;
}

#define tabCapGrow 32

void uiTabAddPage(uiControl *c, const char *name, uiControl *child)
{
	struct tab *t = (struct tab *) (c->data);
	HWND hwnd;
	TCITEMW item;
	LRESULT n;
	uiParent *parent;
	WCHAR *wname;

	if (t->len >= t->cap) {
		t->cap += tabCapGrow;
		t->pages = (uiParent **) uiRealloc(t->pages, t->cap * sizeof (uiParent *), "uiParent *[]");
	}

	hwnd = uiControlHWND(c);
	n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0);

	parent = uiNewParent((uintptr_t) hwnd);
	uiParentSetChild(parent, child);
	uiParentUpdate(parent);
	if (n != 0)		// if this isn't the first page, we have to hide the other controls
		ShowWindow(uiParentHWND(parent), SW_HIDE);
	t->pages[t->len] = parent;
	t->len++;

	ZeroMemory(&item, sizeof (TCITEMW));
	item.mask = TCIF_TEXT;
	wname = toUTF16(name);
	item.pszText = wname;
	// MSDN's example code uses the first invalid index directly for this
	if (SendMessageW(hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1)
		logLastError("error adding tab to Tab in uiTabAddPage()");
	uiFree(wname);

	// if this is the first tab, Windows will automatically show it /without/ sending a TCN_SELCHANGE notification
	// (TODO verify that)
	// so we need to manually resize the tab ourselves
	// don't use uiUpdateParent() for the same reason as in the TCN_SELCHANGE handler
	SendMessageW(uiControlHWND(c), msgUpdateChild, 0, 0);
}