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 }