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