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.treeview;
10 
11 import std.utf : toUTFz;
12 import dguihub.core.utils;
13 import dguihub.core.controls.subclassedcontrol;
14 import dguihub.imagelist;
15 
16 enum NodeInsertMode {
17    head = TVI_FIRST,
18    tail = TVI_LAST,
19 }
20 
21 class TreeNode : Handle!(HTREEITEM) //, IDisposable
22 {
23    private Collection!(TreeNode) _nodes;
24    private TreeView _owner;
25    private TreeNode _parent;
26    private NodeInsertMode _nim;
27    private bool _lazyNode;
28    private bool _childNodesCreated;
29    private string _text;
30    private int _imgIndex;
31    private int _selImgIndex;
32 
33    mixin tagProperty;
34 
35    package this(TreeView owner, string txt, int imgIndex, int selImgIndex, NodeInsertMode nim) {
36       this._childNodesCreated = false;
37       this._owner = owner;
38       this._text = txt;
39       this._imgIndex = imgIndex;
40       this._selImgIndex = selImgIndex;
41       this._nim = nim;
42    }
43 
44    package this(TreeView owner, TreeNode parent, string txt, int imgIndex,
45          int selImgIndex, NodeInsertMode nim) {
46       this._parent = parent;
47       this(owner, txt, imgIndex, selImgIndex, nim);
48    }
49 
50    /*
51 	public ~this()
52 	{
53 		this.dispose();
54 	}
55 
56 	public void dispose()
57 	{
58 		if(this._nodes)
59 		{
60 			this._nodes.clear();
61 		}
62 
63 		this._owner = null;
64 		this._handle = null;
65 		this._parent = null;
66 	}
67 	*/
68 
69    public final TreeNode addNode(string txt, int imgIndex = -1,
70          int selImgIndex = -1, NodeInsertMode nim = NodeInsertMode.tail) {
71       if (!this._nodes) {
72          this._nodes = new Collection!(TreeNode)();
73       }
74 
75       TreeNode tn = new TreeNode(this._owner, this, txt, imgIndex,
76             selImgIndex == -1 ? imgIndex : selImgIndex, nim);
77       this._nodes.add(tn);
78 
79       if (this._owner && this._owner.created) {
80          TreeView.createTreeNode(tn);
81       }
82 
83       return tn;
84    }
85 
86    public final TreeNode addNode(string txt, int imgIndex, NodeInsertMode nim) {
87       return this.addNode(txt, imgIndex, imgIndex, nim);
88    }
89 
90    public final TreeNode addNode(string txt, NodeInsertMode nim) {
91       return this.addNode(txt, -1, -1, nim);
92    }
93 
94    public final void removeNode(TreeNode node) {
95       if (this.created) {
96          TreeView.removeTreeNode(node);
97       }
98 
99       if (this._nodes) {
100          this._nodes.remove(node);
101       }
102    }
103 
104    public final void removeNode(int idx) {
105       TreeNode node = null;
106 
107       if (this._nodes) {
108          node = this._nodes[idx];
109       }
110 
111       if (node) {
112          TreeView.removeTreeNode(node);
113       }
114    }
115 
116    public final void remove() {
117       TreeView.removeTreeNode(this);
118    }
119 
120    @property package NodeInsertMode insertMode() {
121       return this._nim;
122    }
123 
124    @property public final TreeView treeView() {
125       return this._owner;
126    }
127 
128    @property public final TreeNode parent() {
129       return this._parent;
130    }
131 
132    @property public final bool selected() {
133       if (this._owner && this._owner.created) {
134          TVITEMW tvi = void;
135 
136          tvi.mask = TVIF_STATE | TVIF_HANDLE;
137          tvi.hItem = this._handle;
138          tvi.stateMask = TVIS_SELECTED;
139 
140          this._owner.sendMessage(TVM_GETITEMW, 0, cast(LPARAM)&tvi);
141          return (tvi.state & TVIS_SELECTED) ? true : false;
142       }
143 
144       return false;
145    }
146 
147    @property public final bool lazyNode() {
148       return this._lazyNode;
149    }
150 
151    @property public final void lazyNode(bool b) {
152       this._lazyNode = b;
153    }
154 
155    @property public final string text() {
156       return this._text;
157    }
158 
159    @property public final void text(string txt) {
160       this._text = txt;
161 
162       if (this._owner && this._owner.created) {
163          TVITEMW tvi = void;
164 
165          tvi.mask = TVIF_TEXT | TVIF_HANDLE;
166          tvi.hItem = this._handle;
167          tvi.pszText = toUTFz!(wchar*)(txt);
168          this._owner.sendMessage(TVM_SETITEMW, 0, cast(LPARAM)&tvi);
169       }
170    }
171 
172    @property public final int imageIndex() {
173       return this._imgIndex;
174    }
175 
176    @property public final void imageIndex(int idx) {
177       this._imgIndex = idx;
178 
179       if (this._owner && this._owner.created) {
180          TVITEMW tvi = void;
181 
182          tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE;
183          tvi.hItem = this._handle;
184          this._owner.sendMessage(TVM_GETITEMW, 0, cast(LPARAM)&tvi);
185 
186          if (tvi.iSelectedImage == tvi.iImage) //Non e' mai stata assegnata veramente, quindi SelectedImage = Image.
187          {
188             tvi.iSelectedImage = idx;
189          }
190 
191          tvi.iImage = idx;
192          this._owner.sendMessage(TVM_SETITEMW, 0, cast(LPARAM)&tvi);
193       }
194    }
195 
196    @property public final int selectedImageIndex() {
197       return this._selImgIndex;
198    }
199 
200    @property public final void selectedImageIndex(int idx) {
201       this._selImgIndex = idx;
202 
203       if (this._owner && this._owner.created) {
204          TVITEMW tvi = void;
205 
206          tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE;
207          tvi.hItem = this._handle;
208          this._owner.sendMessage(TVM_GETITEMW, 0, cast(LPARAM)&tvi);
209 
210          idx == -1 ? (tvi.iSelectedImage = tvi.iImage) : (tvi.iSelectedImage = idx);
211          this._owner.sendMessage(TVM_SETITEMW, 0, cast(LPARAM)&tvi);
212       }
213    }
214 
215    @property public final TreeNode[] nodes() {
216       if (this._nodes) {
217          return this._nodes.get();
218       }
219 
220       return null;
221    }
222 
223    public final void collapse() {
224       if (this._owner && this._owner.createCanvas() && this.created) {
225          this._owner.sendMessage(TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM)this._handle);
226       }
227    }
228 
229    public final void expand() {
230       if (this._owner && this._owner.createCanvas() && this.created) {
231          this._owner.sendMessage(TVM_EXPAND, TVE_EXPAND, cast(LPARAM)this._handle);
232       }
233    }
234 
235    @property public final bool hasNodes() {
236       return (this._nodes ? true : false);
237    }
238 
239    @property public final int index() {
240       if (this._parent && this._parent.hasNodes) {
241          int i = 0;
242 
243          foreach (TreeNode node; this._parent.nodes) {
244             if (node is this) {
245                return i;
246             }
247 
248             i++;
249          }
250       }
251 
252       return -1;
253    }
254 
255    @property public override HTREEITEM handle() {
256       return super.handle();
257    }
258 
259    @property package void handle(HTREEITEM hTreeNode) {
260       this._handle = hTreeNode;
261    }
262 
263    package void doChildNodes() {
264       if (this._nodes && !this._childNodesCreated) {
265          foreach (TreeNode tn; this._nodes) {
266             if (!tn.created) {
267                TreeView.createTreeNode(tn);
268             }
269          }
270 
271          this._childNodesCreated = true;
272       }
273    }
274 }
275 
276 public alias ItemChangedEventArgs!(TreeNode) TreeNodeChangedEventArgs;
277 public alias ItemEventArgs!(TreeNode) TreeNodeEventArgs;
278 public alias CancelEventArgs!(TreeNode) CancelTreeNodeEventArgs;
279 
280 class TreeView : SubclassedControl {
281    public Event!(Control, CancelTreeNodeEventArgs) selectedNodeChanging;
282    public Event!(Control, TreeNodeChangedEventArgs) selectedNodeChanged;
283    public Event!(Control, CancelTreeNodeEventArgs) treeNodeExpanding;
284    public Event!(Control, TreeNodeEventArgs) treeNodeExpanded;
285    public Event!(Control, TreeNodeEventArgs) treeNodeCollapsed;
286 
287    private Collection!(TreeNode) _nodes;
288    private ImageList _imgList;
289    private TreeNode _selectedNode;
290 
291    public final void clear() {
292       this.sendMessage(TVM_DELETEITEM, 0, cast(LPARAM)TVI_ROOT);
293 
294       if (this._nodes) {
295          this._nodes.clear();
296       }
297    }
298 
299    public final TreeNode addNode(string txt, int imgIndex = -1,
300          int selImgIndex = -1, NodeInsertMode nim = NodeInsertMode.tail) {
301       if (!this._nodes) {
302          this._nodes = new Collection!(TreeNode)();
303       }
304 
305       TreeNode tn = new TreeNode(this, txt, imgIndex, selImgIndex == -1
306             ? imgIndex : selImgIndex, nim);
307       this._nodes.add(tn);
308 
309       if (this.created) {
310          TreeView.createTreeNode(tn);
311       }
312 
313       return tn;
314    }
315 
316    public final TreeNode addNode(string txt, int imgIndex, NodeInsertMode nim) {
317       return this.addNode(txt, imgIndex, imgIndex, nim);
318    }
319 
320    public final TreeNode addNode(string txt, NodeInsertMode nim) {
321       return this.addNode(txt, -1, -1, nim);
322    }
323 
324    public final void removeNode(TreeNode node) {
325       if (this.created) {
326          TreeView.removeTreeNode(node);
327       }
328 
329       if (this._nodes) {
330          this._nodes.remove(node);
331       }
332    }
333 
334    public final void removeNode(int idx) {
335       TreeNode node = null;
336 
337       if (this._nodes) {
338          node = this._nodes[idx];
339       }
340 
341       if (node) {
342          this.removeTreeNode(node);
343       }
344    }
345 
346    @property public final Collection!(TreeNode) nodes() {
347       return this._nodes;
348    }
349 
350    @property public final ImageList imageList() {
351       return this._imgList;
352    }
353 
354    @property public final void imageList(ImageList imgList) {
355       this._imgList = imgList;
356 
357       if (this.created) {
358          this.sendMessage(TVM_SETIMAGELIST, TVSIL_NORMAL, cast(LPARAM)this._imgList.handle);
359       }
360    }
361 
362    @property public final TreeNode selectedNode() {
363       return this._selectedNode;
364    }
365 
366    @property public final void selectedNode(TreeNode node) {
367       this._selectedNode = node;
368 
369       if (this.created) {
370          this.sendMessage(TVM_SELECTITEM, TVGN_FIRSTVISIBLE, cast(LPARAM)node.handle);
371       }
372    }
373 
374    public final void collapse() {
375       if (this.created) {
376          this.sendMessage(TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM)TVI_ROOT);
377       }
378    }
379 
380    public final void expand() {
381       if (this.created) {
382          this.sendMessage(TVM_EXPAND, TVE_EXPAND, cast(LPARAM)TVI_ROOT);
383       }
384    }
385 
386    package static void createTreeNode(TreeNode node) {
387       TVINSERTSTRUCTW tvis;
388 
389       tvis.hParent = (node.parent ? node.parent.handle : cast(HTREEITEM)TVI_ROOT);
390       tvis.hInsertAfter = cast(HTREEITEM)node.insertMode;
391       tvis.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
392       tvis.item.cChildren = I_CHILDRENCALLBACK;
393       tvis.item.iImage = node.imageIndex;
394       tvis.item.iSelectedImage = node.selectedImageIndex;
395       tvis.item.pszText = toUTFz!(wchar*)(node.text);
396       tvis.item.lParam = winCast!(LPARAM)(node);
397 
398       TreeView tvw = node.treeView;
399       node.handle = cast(HTREEITEM)tvw.sendMessage(TVM_INSERTITEMW, 0, cast(LPARAM)&tvis);
400 
401       /*
402 		  *  Performance Killer: simulate a virtual tree view instead
403 		  *
404 		if(node.hasNodes)
405 		{
406 			node.doChildNodes();
407 		}
408 		*/
409 
410       //tvw.redraw();
411    }
412 
413    package static void removeTreeNode(TreeNode node) {
414       node.treeView.sendMessage(TVM_DELETEITEM, 0, cast(LPARAM)node.handle);
415       //node.dispose();
416    }
417 
418    protected override void createControlParams(ref CreateControlParams ccp) {
419       ccp.superclassName = WC_TREEVIEW;
420       ccp.className = WC_DTREEVIEW;
421       this.setStyle(TVS_LINESATROOT | TVS_HASLINES | TVS_HASBUTTONS, true);
422       ccp.defaultBackColor = SystemColors.colorWindow;
423 
424       // Tree view is Double buffered in DGui
425       TreeView.setBit(this._cBits, ControlBits.doubleBuffered, true);
426       super.createControlParams(ccp);
427    }
428 
429    protected override void onHandleCreated(EventArgs e) {
430       if (this._imgList) {
431          this.sendMessage(TVM_SETIMAGELIST, TVSIL_NORMAL, cast(LPARAM)this._imgList.handle);
432       }
433 
434       if (this._nodes) {
435          foreach (TreeNode tn; this._nodes) {
436             TreeView.createTreeNode(tn);
437          }
438       }
439 
440       super.onHandleCreated(e);
441    }
442 
443    protected override void onReflectedMessage(ref Message m) {
444       if (m.msg == WM_NOTIFY) {
445          NMTREEVIEWW* pNotifyTreeView = cast(NMTREEVIEWW*)m.lParam;
446 
447          switch (pNotifyTreeView.hdr.code) {
448          case TVN_GETDISPINFOW: {
449                NMTVDISPINFOW* pTvDispInfo = cast(NMTVDISPINFOW*)m.lParam;
450                TreeNode node = winCast!(TreeNode)(pTvDispInfo.item.lParam);
451 
452                if (node.lazyNode || node.nodes) //Is a Lazy Node, or has childNodes sooner or later a child node will be added
453                {
454                   pTvDispInfo.item.cChildren = node.nodes ? node.nodes.length : 1;
455                } else {
456                   pTvDispInfo.item.cChildren = 0;
457                }
458             }
459             break;
460 
461          case TVN_ITEMEXPANDINGW: {
462                TreeNode node = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam);
463                scope CancelTreeNodeEventArgs e = new CancelTreeNodeEventArgs(node);
464 
465                this.onTreeNodeExpanding(e); //Allow the user to add nodes if e.cancel is 'false'
466 
467                if (!e.cancel && pNotifyTreeView.action & TVE_EXPAND) {
468                   node.doChildNodes();
469                }
470 
471                m.result = e.cancel;
472             }
473             break;
474 
475          case TVN_ITEMEXPANDEDW: {
476                TreeNode node = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam);
477                scope TreeNodeEventArgs e = new TreeNodeEventArgs(node);
478 
479                if (pNotifyTreeView.action & TVE_EXPAND) {
480                   this.onTreeNodeExpanded(e);
481                } else if (pNotifyTreeView.action & TVE_COLLAPSE) {
482                   this.onTreeNodeCollapsed(e);
483                }
484             }
485             break;
486 
487          case TVN_SELCHANGINGW: {
488                TreeNode node = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam);
489                scope CancelTreeNodeEventArgs e = new CancelTreeNodeEventArgs(node);
490                this.onSelectedNodeChanging(e);
491                m.result = e.cancel;
492             }
493             break;
494 
495          case TVN_SELCHANGEDW: {
496                TreeNode oldNode = winCast!(TreeNode)(pNotifyTreeView.itemOld.lParam);
497                TreeNode newNode = winCast!(TreeNode)(pNotifyTreeView.itemNew.lParam);
498 
499                this._selectedNode = newNode;
500                scope TreeNodeChangedEventArgs e = new TreeNodeChangedEventArgs(oldNode, newNode);
501                this.onSelectedNodeChanged(e);
502             }
503             break;
504 
505          case NM_RCLICK: //Trigger a WM_CONTEXMENU Message (Fixes the double click/context menu bug, probably it's a windows bug).
506             Message sm = Message(this._handle, WM_CONTEXTMENU, 0, 0);
507             this.wndProc(sm);
508             m.result = sm.result;
509             break;
510 
511          default:
512             super.onReflectedMessage(m);
513             break;
514          }
515       }
516    }
517 
518    protected void onTreeNodeExpanding(CancelTreeNodeEventArgs e) {
519       this.treeNodeExpanding(this, e);
520    }
521 
522    protected void onTreeNodeExpanded(TreeNodeEventArgs e) {
523       this.treeNodeExpanded(this, e);
524    }
525 
526    protected void onTreeNodeCollapsed(TreeNodeEventArgs e) {
527       this.treeNodeCollapsed(this, e);
528    }
529 
530    protected void onSelectedNodeChanging(CancelTreeNodeEventArgs e) {
531       this.selectedNodeChanging(this, e);
532    }
533 
534    protected void onSelectedNodeChanged(TreeNodeChangedEventArgs e) {
535       this.selectedNodeChanged(this, e);
536    }
537 }