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