1 /** DGui project file.
2 
3 Copyright: Trogu Antonio Davide 2011-2013
4 
5 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 
7 Authors: Trogu Antonio Davide
8 */
9 module dgui.tabcontrol;
10 
11 import std.utf: toUTFz;
12 import dgui.core.controls.subclassedcontrol;
13 import dgui.core.interfaces.ilayoutcontrol;
14 import dgui.layout.panel;
15 import dgui.imagelist;
16 
17 private struct TCItem
18 {
19 	TCITEMHEADERW header;
20 	TabPage page;
21 }
22 
23 enum TabAlignment
24 {
25 	top    = 0,
26 	left   = TCS_VERTICAL,
27 	right  = TCS_VERTICAL | TCS_RIGHT,
28 	bottom = TCS_BOTTOM,
29 }
30 
31 class TabPage: Panel
32 {
33 	private int _imgIndex;
34 	private TabControl _owner;
35 
36 	protected void initTabPage()
37 	{
38 		//Does Nothing
39 	}
40 
41 	@property public final int index()
42 	{
43 		if(this._owner && this._owner.created && this._owner.tabPages)
44 		{
45 			int i = 0;
46 
47 			foreach(TabPage tp; this._owner.tabPages)
48 			{
49 				if(tp is this)
50 				{
51 					return i;
52 				}
53 
54 				i++;
55 			}
56 		}
57 
58 		return -1;
59 	}
60 
61 	@property package void tabControl(TabControl tc)
62 	{
63 		this._owner = tc;
64 	}
65 
66 	@property public final TabControl tabControl()
67 	{
68 		return this._owner;
69 	}
70 
71 	alias @property Control.text text;
72 
73 	@property public override void text(string txt)
74 	{
75 		super.text = txt;
76 
77 		if(this._owner && this._owner.created)
78 		{
79 			TCItem tci = void;
80 
81 			tci.header.mask = TCIF_TEXT;
82 			tci.header.pszText = toUTFz!(wchar*)(txt);
83 
84 			this._owner.sendMessage(TCM_SETITEMW, this.index, cast(LPARAM)&tci);
85 			this.redraw();
86 		}
87 	}
88 
89 	@property public final int imageIndex()
90 	{
91 		return this._imgIndex;
92 	}
93 
94 	@property public final void imageIndex(int idx)
95 	{
96 		this._imgIndex = idx;
97 
98 		if(this._owner && this._owner.created)
99 		{
100 			TCItem tci = void;
101 
102 			tci.header.mask = TCIF_IMAGE;
103 			tci.header.iImage = idx;
104 
105 			this._owner.sendMessage(TCM_SETITEMW, this.index, cast(LPARAM)&tci);
106 		}
107 	}
108 
109 	protected override void createControlParams(ref CreateControlParams ccp)
110 	{
111 		this.setExStyle(WS_EX_STATICEDGE, true);
112 
113 		super.createControlParams(ccp);
114 	}
115 
116 	protected override void onHandleCreated(EventArgs e)
117 	{
118 		this.initTabPage();
119 
120 		super.onHandleCreated(e);
121 	}
122 }
123 
124 alias CancelEventArgs!(TabPage) CancelTabPageEventArgs;
125 
126 class TabControl: SubclassedControl, ILayoutControl
127 {
128 	public Event!(Control, CancelTabPageEventArgs) tabPageChanging;
129 	public Event!(Control, EventArgs) tagPageChanged;
130 
131 	private Collection!(TabPage) _tabPages;
132 	private TabAlignment _ta = TabAlignment.top;
133 	private ImageList _imgList;
134 	private int _selIndex = 0; //By Default: select the first TagPage (if exists)
135 
136 	public final T addPage(T: TabPage = TabPage)(string t, int imgIndex = -1)
137 	{
138 		if(!this._tabPages)
139 		{
140 			this._tabPages = new Collection!(TabPage);
141 		}
142 
143 		T tp = new T();
144 		tp.text = t;
145 		tp.imageIndex = imgIndex;
146 		tp.visible = false;
147 		tp.tabControl = this;
148 		tp.parent = this;
149 
150 		this._tabPages.add(tp);
151 
152 		if(this.created)
153 		{
154 			this.createTabPage(tp);
155 		}
156 
157 		return tp;
158 	}
159 
160 	public final void removePage(int idx)
161 	{
162 		if(this.created)
163 		{
164 			this.removeTabPage(idx);
165 		}
166 
167 		this._tabPages.removeAt(idx);
168 	}
169 
170 	@property public final TabPage[] tabPages()
171 	{
172 		if(this._tabPages)
173 		{
174 			return this._tabPages.get();
175 		}
176 
177 		return null;
178 	}
179 
180 	@property public final TabPage selectedPage()
181 	{
182 		if(this._tabPages)
183 		{
184 			return this._tabPages[this._selIndex];
185 		}
186 
187 		return null;
188 	}
189 
190 	@property public final void selectedPage(TabPage stp)
191 	{
192 		this.selectedIndex = stp.index;
193 	}
194 
195 	@property public final int selectedIndex()
196 	{
197 		return this._selIndex;
198 	}
199 
200 	@property public final void selectedIndex(int idx)
201 	{
202 		if(this._tabPages)
203 		{
204 			TabPage sp = this.selectedPage;   //Old TabPage
205 			TabPage tp = this._tabPages[idx]; //New TabPage
206 
207 			if(sp && sp !is tp)
208 			{
209 				this._selIndex = idx;
210 				tp.visible = true;  //Show new TabPage
211 				sp.visible = false; //Hide old TabPage
212 			}
213 			else if(sp is tp) // Same TabPage, make visibile
214 			{
215 				/*
216 				 * By default, TabPages are created not visible
217 				 */
218 
219 				tp.visible = true;
220 			}
221 
222 			if(this.created)
223 			{
224 				this.updateLayout();
225 			}
226 		}
227 	}
228 
229 	@property public final ImageList imageList()
230 	{
231 		return this._imgList;
232 	}
233 
234 	@property public final void imageList(ImageList imgList)
235 	{
236 		this._imgList = imgList;
237 
238 		if(this.created)
239 		{
240 			this.sendMessage(TCM_SETIMAGELIST, 0, cast(LPARAM)this._imgList.handle);
241 		}
242 	}
243 
244 	@property public final TabAlignment alignment()
245 	{
246 		return this._ta;
247 	}
248 
249 	@property public final void alignment(TabAlignment ta)
250 	{
251 		this.setStyle(this._ta, false);
252 		this.setStyle(ta, true);
253 
254 		this._ta = ta;
255 	}
256 
257 	private void doTabPages()
258 	{
259 		if(this._tabPages)
260 		{
261 			foreach(int i, TabPage tp; this._tabPages)
262 			{
263 				this.createTabPage(tp, false);
264 
265 				if(i == this._selIndex)
266 				{
267 					tp.visible = true;
268 					this.updateLayout();
269 				}
270 			}
271 
272 			this.selectedIndex = this._selIndex;
273 		}
274 	}
275 
276 	public void updateLayout()
277 	{
278 		TabPage selPage = this.selectedPage;
279 
280 		if(selPage)
281 		{
282 			TabControl tc = selPage.tabControl;
283 			Rect adjRect, r = Rect(nullPoint, tc.clientSize);
284 
285 			tc.sendMessage(TCM_ADJUSTRECT, false, cast(LPARAM)&adjRect.rect);
286 
287 			r.left += adjRect.left;
288 			r.top += adjRect.top;
289 			r.right += r.left + adjRect.width;
290 			r.bottom += r.top + adjRect.height;
291 
292 			selPage.bounds = r; //selPage docks its child componentsS
293 		}
294 	}
295 
296 	private void createTabPage(TabPage tp, bool adding = true)
297 	{
298 		TCItem tci;
299 		tci.header.mask = TCIF_IMAGE | TCIF_TEXT | TCIF_PARAM;
300 		tci.header.iImage = tp.imageIndex;
301 		tci.header.pszText = toUTFz!(wchar*)(tp.text);
302 		tci.page = tp;
303 
304 		tp.sendMessage(DGUI_CREATEONLY, 0, 0); //Calls Control.create()
305 
306 		int idx = tp.index;
307 		this.sendMessage(TCM_INSERTITEMW, idx, cast(LPARAM)&tci);
308 
309 		if(adding) //Adding mode: select the last TabPage
310 		{
311 			this.sendMessage(TCM_SETCURSEL, idx, 0);
312 			this.selectedIndex = idx;
313 		}
314 	}
315 
316 	private void removeTabPage(int idx)
317 	{
318 		if(this._tabPages)
319 		{
320 			if(idx == this._selIndex)
321 			{
322 				this.selectedIndex = idx > 0 ? idx - 1 : 0;
323 			}
324 
325 			if(this.created)
326 			{
327 				this.sendMessage(TCM_DELETEITEM, idx, 0);
328 				this.sendMessage(TCM_SETCURSEL, this._selIndex, 0); //Set the new tab's index
329 			}
330 
331 			TabPage tp = this._tabPages[idx];
332 			tp.dispose();
333 		}
334 	}
335 
336 	protected override void createControlParams(ref CreateControlParams ccp)
337 	{
338 		this.setStyle(WS_CLIPCHILDREN | WS_CLIPSIBLINGS, true);
339 		this.setExStyle(WS_EX_CONTROLPARENT, true);
340 
341 		ccp.superclassName = WC_TABCONTROL;
342 		ccp.className = WC_DTABCONTROL;
343 
344 		super.createControlParams(ccp);
345 	}
346 
347 	protected override void onHandleCreated(EventArgs e)
348 	{
349 		if(this._imgList)
350 		{
351 			this.sendMessage(TCM_SETIMAGELIST, 0, cast(LPARAM)this._imgList.handle);
352 		}
353 
354 		this.doTabPages();
355 		super.onHandleCreated(e);
356 	}
357 
358 	protected override void onReflectedMessage(ref Message m)
359 	{
360 		if(m.msg == WM_NOTIFY)
361 		{
362 			NMHDR* pNotify = cast(NMHDR*)m.lParam;
363 
364 			switch(pNotify.code)
365 			{
366 				case TCN_SELCHANGING:
367 					scope CancelTabPageEventArgs e = new CancelTabPageEventArgs(this.selectedPage);
368 					this.onTabPageChanging(e);
369 					m.result = e.cancel;
370 					break;
371 
372 				case TCN_SELCHANGE:
373 					this.selectedIndex = this.sendMessage(TCM_GETCURSEL, 0, 0);
374 					this.onTabPageChanged(EventArgs.empty);
375 					break;
376 
377 				default:
378 					break;
379 			}
380 		}
381 
382 		super.onReflectedMessage(m);
383 	}
384 
385 	protected override void show()
386 	{
387 		super.show();
388 		this.updateLayout();
389 	}
390 
391 	protected override void onResize(EventArgs e)
392 	{
393 		this.updateLayout();
394 		super.onResize(e);
395 	}
396 
397 	protected void onTabPageChanging(CancelTabPageEventArgs e)
398 	{
399 		this.tabPageChanging(this, e);
400 	}
401 
402 	protected void onTabPageChanged(EventArgs e)
403 	{
404 		this.tagPageChanged(this, e);
405 	}
406 }