1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 module dfl.treeview;
5 import core.sys.windows.windows;
6 import core.sys.windows.commctrl;
7 
8 import dfl.exception;
9 import dfl.internal.dlib;
10 import dfl.control;
11 import dfl.application;
12 import dfl.base;
13 import dfl.event;
14 import dfl.drawing;
15 import dfl.collections;
16 import dfl.internal.utf;
17 
18 version (DFL_NO_IMAGELIST) {
19 } else {
20    private import dfl.imagelist;
21 }
22 
23 private extern (Windows) void _initTreeview();
24 
25 enum TreeViewAction : ubyte {
26    UNKNOWN,
27    COLLAPSE,
28    EXPAND,
29    BY_KEYBOARD,
30    BY_MOUSE,
31 }
32 
33 class TreeViewCancelEventArgs : CancelEventArgs {
34    this(TreeNode node, bool cancel, TreeViewAction action) {
35       super(cancel);
36 
37       _node = node;
38       _action = action;
39    }
40 
41    final @property TreeViewAction action() {
42       return _action;
43    }
44 
45    final @property TreeNode node() {
46       return _node;
47    }
48 
49 private:
50    TreeNode _node;
51    TreeViewAction _action;
52 }
53 
54 class TreeViewEventArgs : EventArgs {
55    this(TreeNode node, TreeViewAction action) {
56       _node = node;
57       _action = action;
58    }
59 
60    this(TreeNode node) {
61       _node = node;
62       //_action = TreeViewAction.UNKNOWN;
63    }
64 
65    final @property TreeViewAction action() {
66       return _action;
67    }
68 
69    final @property TreeNode node() {
70       return _node;
71    }
72 
73 private:
74    TreeNode _node;
75    TreeViewAction _action = TreeViewAction.UNKNOWN;
76 }
77 
78 class NodeLabelEditEventArgs : EventArgs {
79    this(TreeNode node, Dstring label) {
80       _node = node;
81       _label = label;
82    }
83 
84    this(TreeNode node) {
85       _node = node;
86    }
87 
88    final @property TreeNode node() {
89       return _node;
90    }
91 
92    final @property Dstring label() {
93       return _label;
94    }
95 
96    final @property void cancelEdit(bool byes) {
97       _cancel = byes;
98    }
99 
100    final @property bool cancelEdit() {
101       return _cancel;
102    }
103 
104 private:
105    TreeNode _node;
106    Dstring _label;
107    bool _cancel = false;
108 }
109 
110 class TreeNode : DObject {
111    this(Dstring labelText) {
112       this();
113 
114       ttext = labelText;
115    }
116 
117    this(Dstring labelText, TreeNode[] children) {
118       this();
119 
120       ttext = labelText;
121       tchildren.addRange(children);
122    }
123 
124    this() {
125       Application.ppin(cast(void*) this);
126 
127       /*
128       bcolor = Color.empty;
129       fcolor = Color.empty;
130       */
131 
132       tchildren = new TreeNodeCollection(tview, this);
133    }
134 
135    this(Object val) { // package
136       this(getObjectString(val));
137    }
138 
139    //final @property void backColor(Color c) {
140    //if(created) {
141    //COLORREF cref;
142    //if(Color.empty == c) {
143    //cref = CLR_NONE;
144    //} else {
145    //cref = c.toRgb();
146    //}
147    ////SendMessageA(tview.hwnd, TVM_SETBKCOLOR, 0, cast(LPARAM)cref);
148    //prevwproc(LVM_SETBKCOLOR, 0, cast(LPARAM)cref);
149    //prevwproc(LVM_SETTEXTBKCOLOR, 0, cast(LPARAM)cref);
150    //}
151    //super.backColor = c;
152    //}
153 
154    //final @property Color backColor() {
155    //if(Color.empty == backc)
156    //return defaultBackColor;
157    //return backc;
158    //}
159 
160    /*
161       final @property void backColor(Color c)
162       {
163       bcolor = c;
164       }
165 
166       final @property Color backColor()
167       {
168       return bcolor;
169       }
170     */
171 
172    final @property Rect bounds() {
173       Rect result;
174 
175       if (created) {
176          RECT rect;
177          *(cast(HTREEITEM*)&rect) = hnode;
178          if (SendMessageA(tview.handle, TVM_GETITEMRECT, FALSE, cast(LPARAM)&rect)) {
179             result = Rect(&rect);
180          }
181       }
182 
183       return result;
184    }
185 
186    final @property TreeNode firstNode() {
187       if (tchildren.length) {
188          return tchildren._nodes[0];
189       }
190       return null;
191    }
192 
193    /*
194       final @property void foreColor(Color c)
195       {
196       fcolor = c;
197       }
198 
199       final @property Color foreColor()
200       {
201       return fcolor;
202       }
203     */
204 
205    // Path from the root to this node.
206    final @property Dstring fullPath() {
207       if (!tparent) {
208          return ttext;
209       }
210 
211       // Might want to manually loop through parents and preallocate the whole buffer.
212       assert(tview !is null);
213       dchar sep;
214       sep = tview.pathSeparator;
215       //return std.string.format("%s%s%s", tparent.fullPath, sep, ttext);
216       char[4] ssep;
217       int sseplen = 0;
218       foreach (char ch; (&sep)[0 .. 1]) {
219          ssep[sseplen++] = ch;
220       }
221       //return tparent.fullPath ~ ssep[0 .. sseplen] ~ ttext;
222       return tparent.fullPath ~ cast(Dstring) ssep[0 .. sseplen] ~ ttext; // Needed in D2.
223    }
224 
225    final @property HTREEITEM handle() {
226       return hnode;
227    }
228 
229    // Index of this node in the parent node.
230    final @property int index() {
231       int result = -1;
232       if (tparent) {
233          result = tparent.tchildren.indexOf(this);
234          assert(result != -1);
235       }
236       return result;
237    }
238 
239    /*
240       final @property bool isEditing()
241       {
242       }
243     */
244 
245    final @property bool isExpanded() {
246       return isState(TVIS_EXPANDED);
247    }
248 
249    final @property bool isSelected() {
250       return isState(TVIS_SELECTED);
251    }
252 
253    /*
254       final @property bool isVisible()
255       {
256       }
257     */
258 
259    final @property TreeNode lastNode() {
260       if (tchildren.length) {
261          return tchildren._nodes[tchildren.length - 1];
262       }
263       return null;
264    }
265 
266    // Next sibling node.
267    final @property TreeNode nextNode() {
268       if (tparent) {
269          int i;
270          i = tparent.tchildren.indexOf(this);
271          assert(i != -1);
272 
273          i++;
274          if (i != tparent.tchildren.length) {
275             return tparent.tchildren._nodes[i];
276          }
277       }
278       return null;
279    }
280 
281    /*
282 
283       final @property void nodeFont(Font f)
284       {
285       tfont = f;
286       }
287 
288 
289       final @property Font nodeFont()
290       {
291       return tfont;
292       }
293     */
294 
295    final @property TreeNodeCollection nodes() {
296       return tchildren;
297    }
298 
299    final @property TreeNode parent() {
300       return tparent;
301    }
302 
303    // Previous sibling node.
304    final @property TreeNode prevNode() {
305       if (tparent) {
306          int i;
307          i = tparent.tchildren.indexOf(this);
308          assert(i != -1);
309 
310          if (i) {
311             i--;
312             return tparent.tchildren._nodes[i];
313          }
314       }
315       return null;
316    }
317 
318    final @property void tag(Object o) {
319       ttag = o;
320    }
321 
322    final @property Object tag() {
323       return ttag;
324    }
325 
326    final @property void text(Dstring newText) {
327       ttext = newText;
328 
329       if (created) {
330          TV_ITEMA item;
331          Message m;
332 
333          item.mask = TVIF_HANDLE | TVIF_TEXT;
334          item.hItem = hnode;
335          /*
336             item.pszText = stringToStringz(ttext);
337          //item.cchTextMax = ttext.length; // ?
338          m = Message(tview.handle, TVM_SETITEMA, 0, cast(LPARAM)&item);
339           */
340          if (dfl.internal.utf.useUnicode) {
341             item.pszText = cast(typeof(item.pszText)) dfl.internal.utf.toUnicodez(ttext);
342             m = Message(tview.handle, TVM_SETITEMW, 0, cast(LPARAM)&item);
343          } else {
344             item.pszText = cast(typeof(item.pszText)) dfl.internal.utf.unsafeAnsiz(ttext);
345             m = Message(tview.handle, TVM_SETITEMA, 0, cast(LPARAM)&item);
346          }
347          tview.prevWndProc(m);
348       }
349    }
350 
351    final @property Dstring text() {
352       return ttext;
353    }
354 
355    // Get the TreeView control this node belongs to.
356    final @property TreeView treeView() {
357       return tview;
358    }
359 
360    final void beginEdit() {
361       if (created) {
362          SetFocus(tview.hwnd); // Needs to have focus.
363          HWND hwEdit;
364          hwEdit = cast(HWND) SendMessageA(tview.hwnd, TVM_EDITLABELA, 0, cast(LPARAM) hnode);
365          if (!hwEdit) {
366             goto err_edit;
367          }
368       } else {
369       err_edit:
370          throw new DflException("Unable to edit TreeNode");
371       }
372    }
373 
374    /*
375 
376       final void endEdit(bool cancel)
377       {
378    // ?
379    }
380     */
381 
382    final void ensureVisible() {
383       if (created) {
384          SendMessageA(tview.hwnd, TVM_ENSUREVISIBLE, 0, cast(LPARAM) hnode);
385       }
386    }
387 
388    final void collapse() {
389       if (created) {
390          SendMessageA(tview.hwnd, TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM) hnode);
391       }
392    }
393 
394    final void expand() {
395       if (created) {
396          SendMessageA(tview.hwnd, TVM_EXPAND, TVE_EXPAND, cast(LPARAM) hnode);
397       }
398    }
399 
400    final void expandAll() {
401       if (created) {
402          SendMessageA(tview.hwnd, TVM_EXPAND, TVE_EXPAND, cast(LPARAM) hnode);
403 
404          foreach (TreeNode node; tchildren._nodes) {
405             node.expandAll();
406          }
407       }
408    }
409 
410    static TreeNode fromHandle(TreeView tree, HTREEITEM handle) {
411       return tree.treeNodeFromHandle(handle);
412    }
413 
414    final void remove() {
415       if (tparent) {
416          tparent.tchildren.remove(this);
417       } else if (tview) { // It's a top level node.
418          tview.tchildren.remove(this);
419       }
420    }
421 
422    final void toggle() {
423       if (created) {
424          SendMessageA(tview.hwnd, TVM_EXPAND, TVE_TOGGLE, cast(LPARAM) hnode);
425       }
426    }
427 
428    version (DFL_NO_IMAGELIST) {
429    } else {
430       final @property void imageIndex(int index) {
431          this._imgidx = index;
432 
433          if (created) {
434             TV_ITEMA item;
435             Message m;
436             m = Message(tview.handle, TVM_SETITEMA, 0, cast(LPARAM)&item);
437 
438             item.mask = TVIF_HANDLE | TVIF_IMAGE;
439             item.hItem = hnode;
440             item.iImage = _imgidx;
441             if (tview._selimgidx < 0) {
442                item.mask |= TVIF_SELECTEDIMAGE;
443                item.iSelectedImage = _imgidx;
444             }
445             tview.prevWndProc(m);
446          }
447       }
448 
449       final @property int imageIndex() {
450          return _imgidx;
451       }
452    }
453 
454    override Dstring toString() {
455       return ttext;
456    }
457 
458    override Dequ opEquals(Object o) {
459       return 0 == stringICmp(ttext, getObjectString(o)); // ?
460    }
461 
462    Dequ opEquals(TreeNode node) {
463       return 0 == stringICmp(ttext, node.ttext);
464    }
465 
466    Dequ opEquals(Dstring val) {
467       return 0 == stringICmp(ttext, val);
468    }
469 
470    override int opCmp(Object o) {
471       return stringICmp(ttext, getObjectString(o)); // ?
472    }
473 
474    int opCmp(TreeNode node) {
475       return stringICmp(ttext, node.ttext);
476    }
477 
478    int opCmp(Dstring val) {
479       return stringICmp(text, val);
480    }
481 
482 private:
483    Dstring ttext;
484    TreeNode tparent;
485    TreeNodeCollection tchildren;
486    Object ttag;
487    HTREEITEM hnode;
488    TreeView tview;
489    version (DFL_NO_IMAGELIST) {
490    } else {
491       int _imgidx = -1;
492    }
493    /*
494       Color bcolor, fcolor;
495       Font tfont;
496     */
497 
498    package final @property bool created() {
499       if (tview && tview.created()) {
500          assert(hnode);
501          return true;
502       }
503       return false;
504    }
505 
506    bool isState(UINT state) {
507       if (created) {
508          TV_ITEMA ti;
509          ti.mask = TVIF_HANDLE | TVIF_STATE;
510          ti.hItem = hnode;
511          ti.stateMask = state;
512          if (SendMessageA(tview.handle, TVM_GETITEMA, 0, cast(LPARAM)&ti)) {
513             if (ti.state & state) {
514                return true;
515             }
516          }
517       }
518       return false;
519    }
520 
521    void _reset() {
522       hnode = null;
523       tview = null;
524       tparent = null;
525    }
526 }
527 
528 class TreeNodeCollection {
529    void add(TreeNode node) {
530       //cprintf("Adding node %p '%.*s'\n", cast(void*)node, getObjectString(node));
531 
532       int i;
533 
534       if (tview && tview.sorted()) {
535          // Insertion sort.
536 
537          for (i = 0; i != _nodes.length; i++) {
538             if (node < _nodes[i]) {
539                break;
540             }
541          }
542       } else {
543          i = _nodes.length;
544       }
545 
546       insert(i, node);
547    }
548 
549    void add(Dstring text) {
550       return add(new TreeNode(text));
551    }
552 
553    void add(Object val) {
554       return add(new TreeNode(getObjectString(val))); // ?
555    }
556 
557    void addRange(Object[] range) {
558       foreach (Object o; range) {
559          add(o);
560       }
561    }
562 
563    void addRange(TreeNode[] range) {
564       foreach (TreeNode node; range) {
565          add(node);
566       }
567    }
568 
569    void addRange(Dstring[] range) {
570       foreach (Dstring s; range) {
571          add(s);
572       }
573    }
574 
575    // Like clear but doesn't bother removing stuff from the lists.
576    // Used when a parent is being removed and the children only
577    // need to be reset.
578    private void _reset() {
579       foreach (TreeNode node; _nodes) {
580          node._reset();
581       }
582    }
583 
584    // Clear node handles when the TreeView window is destroyed so
585    // that it can be reconstructed.
586    private void _resetHandles() {
587       foreach (TreeNode node; _nodes) {
588          node.tchildren._resetHandles();
589          node.hnode = null;
590       }
591    }
592 
593 private:
594 
595    TreeView tview; // null if not assigned to a TreeView yet.
596    TreeNode tparent; // null if root. The parent of -_nodes-.
597    TreeNode[] _nodes;
598 
599    void verifyNoParent(TreeNode node) {
600       if (node.tparent) {
601          throw new DflException("TreeNode already belongs to a TreeView");
602       }
603    }
604 
605    package this(TreeView treeView, TreeNode parentNode) {
606       tview = treeView;
607       tparent = parentNode;
608    }
609 
610    package final void setTreeView(TreeView treeView) {
611       tview = treeView;
612       foreach (TreeNode node; _nodes) {
613          node.tchildren.setTreeView(treeView);
614       }
615    }
616 
617    package final @property bool created() {
618       return tview && tview.created();
619    }
620 
621    package void populateInsertChildNode(ref Message m, ref TV_ITEMA dest, TreeNode node) {
622       with (dest) {
623          mask = /* TVIF_CHILDREN | */ TVIF_PARAM | TVIF_TEXT;
624          version (DFL_NO_IMAGELIST) {
625          } else {
626             mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE;
627             iImage = node._imgidx;
628             if (tview._selimgidx < 0) {
629                iSelectedImage = node._imgidx;
630             } else {
631                iSelectedImage = tview._selimgidx;
632             }
633          }
634          /* cChildren = I_CHILDRENCALLBACK; */
635          lParam = cast(LPARAM) cast(void*) node;
636          /*
637             pszText = stringToStringz(node.text);
638          //cchTextMax = node.text.length; // ?
639           */
640          if (dfl.internal.utf.useUnicode) {
641             pszText = cast(typeof(pszText)) dfl.internal.utf.toUnicodez(node.text);
642             m.hWnd = tview.handle;
643             m.msg = TVM_INSERTITEMW;
644          } else {
645             pszText = cast(typeof(pszText)) dfl.internal.utf.unsafeAnsiz(node.text);
646             m.hWnd = tview.handle;
647             m.msg = TVM_INSERTITEMA;
648          }
649       }
650    }
651 
652    void doNodes()
653    in {
654       assert(created);
655    }
656    body {
657       TV_INSERTSTRUCTA tis;
658       Message m;
659 
660       tis.hInsertAfter = TVI_LAST;
661 
662       m.hWnd = tview.handle;
663       m.wParam = 0;
664 
665       foreach (TreeNode node; _nodes) {
666          assert(!node.handle);
667 
668          tis.hParent = tparent ? tparent.handle : TVI_ROOT;
669          populateInsertChildNode(m, tis.item, node);
670 
671          m.lParam = cast(LPARAM)&tis;
672          tview.prevWndProc(m);
673          assert(m.result);
674          node.hnode = cast(HTREEITEM) m.result;
675 
676          node.tchildren.doNodes();
677       }
678    }
679 
680    void _added(size_t idx, TreeNode val) {
681       verifyNoParent(val);
682 
683       val.tparent = tparent;
684       val.tview = tview;
685       val.tchildren.setTreeView(tview);
686 
687       if (created) {
688          TV_INSERTSTRUCTA tis;
689 
690          if (idx <= 0) {
691             tis.hInsertAfter = TVI_FIRST;
692          } else if (idx >= cast(int) _nodes.length) {
693             tis.hInsertAfter = TVI_LAST;
694          } else {
695             tis.hInsertAfter = _nodes[idx - 1].handle;
696          }
697 
698          tis.hParent = tparent ? tparent.handle : TVI_ROOT;
699          assert(tis.hInsertAfter);
700 
701          Message m;
702          m.wParam = 0;
703 
704          populateInsertChildNode(m, tis.item, val);
705 
706          m.lParam = cast(LPARAM)&tis;
707          tview.prevWndProc(m);
708          assert(m.result);
709          val.hnode = cast(HTREEITEM) m.result;
710 
711          val.tchildren.doNodes();
712 
713          if (tparent) {
714             tview.invalidate(tparent.bounds);
715          }
716       }
717    }
718 
719    void _removing(size_t idx, TreeNode val) {
720       if (size_t.max == idx) { // Clearing all...
721          TreeNode[] nodes = _nodes;
722          _nodes = _nodes[0 .. 0]; // Not nice to dfl.collections, but OK.
723          if (created) {
724             Message m;
725             m.hWnd = tview.handle;
726             m.msg = TVM_DELETEITEM;
727             m.wParam = 0;
728             if (tparent) {
729                foreach (TreeNode node; nodes) {
730                   assert(node.handle !is null);
731                   m.lParam = cast(LPARAM) node.handle;
732                   tview.prevWndProc(m);
733 
734                   node._reset();
735                }
736             } else {
737                m.lParam = cast(LPARAM) TVI_ROOT;
738                tview.prevWndProc(m);
739                foreach (TreeNode node; nodes) {
740                   node._reset();
741                }
742             }
743          }
744       } else {
745       }
746    }
747 
748    void _removed(size_t idx, TreeNode val) {
749       if (size_t.max == idx) { // Clear all.
750       } else {
751          if (created) {
752             assert(val.hnode);
753             Message m;
754             m = Message(tview.handle, TVM_DELETEITEM, 0, cast(LPARAM) val.hnode);
755             tview.prevWndProc(m);
756          }
757 
758          // Clear children.
759          val._reset();
760       }
761    }
762 
763 public:
764 
765    mixin ListWrapArray!(TreeNode, _nodes, _blankListCallback!(TreeNode),
766       _added, _removing, _removed, true, /*true*/ false, false) _wraparray;
767 }
768 
769 class TreeView : ControlSuperClass {
770    this() {
771       _initTreeview();
772 
773       wstyle |= WS_TABSTOP | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES;
774       wexstyle |= WS_EX_CLIENTEDGE;
775       ctrlStyle |= ControlStyles.SELECTABLE;
776       wclassStyle = treeviewClassStyle;
777 
778       tchildren = new TreeNodeCollection(this, null);
779    }
780 
781    /*
782       ~this()
783       {
784       if(tchildren)
785       tchildren._dtorReset();
786       }
787     */
788 
789    static @property Color defaultBackColor() {
790       return SystemColors.window;
791    }
792 
793    override @property Color backColor() {
794       if (Color.empty == backc) {
795          return defaultBackColor;
796       }
797       return backc;
798    }
799 
800    override @property void backColor(Color b) {
801       super.backColor = b;
802 
803       if (created) {
804          // For some reason the left edge isn't showing the new color.
805          // This causes the entire control to be redrawn with the new color.
806          // Sets the same font.
807          prevwproc(WM_SETFONT, this.font ? cast(WPARAM) this.font.handle : 0, MAKELPARAM(TRUE,
808             0));
809       }
810    }
811 
812    static @property Color defaultForeColor() {
813       return SystemColors.windowText;
814    }
815 
816    override @property Color foreColor() {
817       if (Color.empty == forec) {
818          return defaultForeColor;
819       }
820       return forec;
821    }
822 
823    alias foreColor = Control.foreColor; // Overload.
824 
825    final @property void borderStyle(BorderStyle bs) {
826       final switch (bs) {
827       case BorderStyle.FIXED_3D:
828          _style(_style() & ~WS_BORDER);
829          _exStyle(_exStyle() | WS_EX_CLIENTEDGE);
830          break;
831 
832       case BorderStyle.FIXED_SINGLE:
833          _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
834          _style(_style() | WS_BORDER);
835          break;
836 
837       case BorderStyle.NONE:
838          _style(_style() & ~WS_BORDER);
839          _exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
840          break;
841       }
842 
843       if (created) {
844          redrawEntire();
845       }
846    }
847 
848    final @property BorderStyle borderStyle() {
849       if (_exStyle() & WS_EX_CLIENTEDGE) {
850          return BorderStyle.FIXED_3D;
851       } else if (_style() & WS_BORDER) {
852          return BorderStyle.FIXED_SINGLE;
853       }
854       return BorderStyle.NONE;
855    }
856 
857    /*
858 
859       final @property void checkBoxes(bool byes)
860       {
861       if(byes)
862       _style(_style() | TVS_CHECKBOXES);
863       else
864       _style(_style() & ~TVS_CHECKBOXES);
865 
866       _crecreate();
867       }
868 
869 
870       final @property bool checkBoxes()
871       {
872       return (_style() & TVS_CHECKBOXES) != 0;
873       }
874     */
875 
876    final @property void fullRowSelect(bool byes) {
877       if (byes) {
878          _style(_style() | TVS_FULLROWSELECT);
879       } else {
880          _style(_style() & ~TVS_FULLROWSELECT);
881       }
882 
883       _crecreate(); // ?
884    }
885 
886    final @property bool fullRowSelect() {
887       return (_style() & TVS_FULLROWSELECT) != 0;
888    }
889 
890    final @property void hideSelection(bool byes) {
891       if (byes) {
892          _style(_style() & ~TVS_SHOWSELALWAYS);
893       } else {
894          _style(_style() | TVS_SHOWSELALWAYS);
895       }
896    }
897 
898    final @property bool hideSelection() {
899       return (_style() & TVS_SHOWSELALWAYS) == 0;
900    }
901 
902    deprecated alias hotTracking = hoverSelection;
903 
904    final @property void hoverSelection(bool byes) {
905       if (byes) {
906          _style(_style() | TVS_TRACKSELECT);
907       } else {
908          _style(_style() & ~TVS_TRACKSELECT);
909       }
910    }
911 
912    final @property bool hoverSelection() {
913       return (_style() & TVS_TRACKSELECT) != 0;
914    }
915 
916    final @property void indent(int newIndent) {
917       if (newIndent < 0) {
918          newIndent = 0;
919       } else if (newIndent > 32_000) {
920          newIndent = 32_000;
921       }
922 
923       ind = newIndent;
924 
925       if (created) {
926          SendMessageA(hwnd, TVM_SETINDENT, ind, 0);
927       }
928    }
929 
930    final @property int indent() {
931       if (created) {
932          ind = cast(int) SendMessageA(hwnd, TVM_GETINDENT, 0, 0);
933       }
934       return ind;
935    }
936 
937    final @property void itemHeight(int h) {
938       if (h < 0) {
939          h = 0;
940       }
941 
942       iheight = h;
943 
944       if (created) {
945          SendMessageA(hwnd, TVM_SETITEMHEIGHT, iheight, 0);
946       }
947    }
948 
949    final @property int itemHeight() {
950       if (created) {
951          iheight = cast(int) SendMessageA(hwnd, TVM_GETITEMHEIGHT, 0, 0);
952       }
953       return iheight;
954    }
955 
956    final @property void labelEdit(bool byes) {
957       if (byes) {
958          _style(_style() | TVS_EDITLABELS);
959       } else {
960          _style(_style() & ~TVS_EDITLABELS);
961       }
962    }
963 
964    final @property bool labelEdit() {
965       return (_style() & TVS_EDITLABELS) != 0;
966    }
967 
968    final @property TreeNodeCollection nodes() {
969       return tchildren;
970    }
971 
972    final @property void pathSeparator(dchar sep) {
973       pathsep = sep;
974    }
975 
976    final @property dchar pathSeparator() {
977       return pathsep;
978    }
979 
980    final @property void scrollable(bool byes) {
981       if (byes) {
982          _style(_style() & ~TVS_NOSCROLL);
983       } else {
984          _style(_style() | TVS_NOSCROLL);
985       }
986 
987       if (created) {
988          redrawEntire();
989       }
990    }
991 
992    final @property bool scrollable() {
993       return (_style & TVS_NOSCROLL) == 0;
994    }
995 
996    final @property void selectedNode(TreeNode node) {
997       if (created) {
998          if (node) {
999             SendMessageA(hwnd, TVM_SELECTITEM, TVGN_CARET, cast(LPARAM) node.handle);
1000          } else {
1001             // Should the selection be cleared if -node- is null?
1002             //SendMessageA(hwnd, TVM_SELECTITEM, TVGN_CARET, cast(LPARAM)null);
1003          }
1004       }
1005    }
1006 
1007    final @property TreeNode selectedNode() {
1008       if (created) {
1009          HTREEITEM hnode;
1010          hnode = cast(HTREEITEM) SendMessageA(hwnd, TVM_GETNEXTITEM, TVGN_CARET, cast(LPARAM) 0);
1011          if (hnode) {
1012             return treeNodeFromHandle(hnode);
1013          }
1014       }
1015       return null;
1016    }
1017 
1018    final @property void showLines(bool byes) {
1019       if (byes) {
1020          _style(_style() | TVS_HASLINES);
1021       } else {
1022          _style(_style() & ~TVS_HASLINES);
1023       }
1024 
1025       _crecreate(); // ?
1026    }
1027 
1028    final @property bool showLines() {
1029       return (_style() & TVS_HASLINES) != 0;
1030    }
1031 
1032    final @property void showPlusMinus(bool byes) {
1033       if (byes) {
1034          _style(_style() | TVS_HASBUTTONS);
1035       } else {
1036          _style(_style() & ~TVS_HASBUTTONS);
1037       }
1038 
1039       _crecreate(); // ?
1040    }
1041 
1042    final @property bool showPlusMinus() {
1043       return (_style() & TVS_HASBUTTONS) != 0;
1044    }
1045 
1046    // -showPlusMinus- should be false.
1047    final @property void singleExpand(bool byes) {
1048       if (byes) {
1049          _style(_style() | TVS_SINGLEEXPAND);
1050       } else {
1051          _style(_style() & ~TVS_SINGLEEXPAND);
1052       }
1053 
1054       _crecreate(); // ?
1055    }
1056 
1057    final @property bool singleExpand() {
1058       return (_style & TVS_SINGLEEXPAND) != 0;
1059    }
1060 
1061    final @property void showRootLines(bool byes) {
1062       if (byes) {
1063          _style(_style() | TVS_LINESATROOT);
1064       } else {
1065          _style(_style() & ~TVS_LINESATROOT);
1066       }
1067 
1068       _crecreate(); // ?
1069    }
1070 
1071    final @property bool showRootLines() {
1072       return (_style() & TVS_LINESATROOT) != 0;
1073    }
1074 
1075    final @property void sorted(bool byes) {
1076       _sort = byes;
1077    }
1078 
1079    final @property bool sorted() {
1080       return _sort;
1081    }
1082 
1083    // First visible node, based on the scrolled position.
1084    final @property TreeNode topNode() {
1085       if (created) {
1086          HTREEITEM hnode;
1087          hnode = cast(HTREEITEM) SendMessageA(hwnd, TVM_GETNEXTITEM,
1088             TVGN_FIRSTVISIBLE, cast(LPARAM) 0);
1089          if (hnode) {
1090             return treeNodeFromHandle(hnode);
1091          }
1092       }
1093       return null;
1094    }
1095 
1096    // Number of visible nodes, including partially visible.
1097    final @property int visibleCount() {
1098       if (!created) {
1099          return 0;
1100       }
1101       return cast(int) SendMessageA(hwnd, TVM_GETVISIBLECOUNT, 0, 0);
1102    }
1103 
1104    final void beginUpdate() {
1105       SendMessageA(handle, WM_SETREDRAW, false, 0);
1106    }
1107 
1108    final void endUpdate() {
1109       SendMessageA(handle, WM_SETREDRAW, true, 0);
1110       invalidate(true); // Show updates.
1111    }
1112 
1113    final void collapseAll() {
1114       if (created) {
1115          void collapsing(TreeNodeCollection tchildren) {
1116             foreach (TreeNode node; tchildren._nodes) {
1117                SendMessageA(hwnd, TVM_EXPAND, TVE_COLLAPSE, cast(LPARAM) node.hnode);
1118                collapsing(node.tchildren);
1119             }
1120          }
1121 
1122          collapsing(tchildren);
1123       }
1124    }
1125 
1126    final void expandAll() {
1127       if (created) {
1128          void expanding(TreeNodeCollection tchildren) {
1129             foreach (TreeNode node; tchildren._nodes) {
1130                SendMessageA(hwnd, TVM_EXPAND, TVE_EXPAND, cast(LPARAM) node.hnode);
1131                expanding(node.tchildren);
1132             }
1133          }
1134 
1135          expanding(tchildren);
1136       }
1137    }
1138 
1139    final TreeNode getNodeAt(int x, int y) {
1140       if (created) {
1141          TVHITTESTINFO thi;
1142          HTREEITEM hti;
1143          thi.pt.x = x;
1144          thi.pt.y = y;
1145          hti = cast(HTREEITEM) SendMessageA(hwnd, TVM_HITTEST, 0, cast(LPARAM)&thi);
1146          if (hti) {
1147             TreeNode result;
1148             result = treeNodeFromHandle(hti);
1149             if (result) {
1150                assert(result.tview is this);
1151                return result;
1152             }
1153          }
1154       }
1155       return null;
1156    }
1157 
1158    final TreeNode getNodeAt(Point pt) {
1159       return getNodeAt(pt.x, pt.y);
1160    }
1161 
1162    /*
1163 
1164    // TODO: finish.
1165    final int getNodeCount(bool includeSubNodes)
1166    {
1167    int result;
1168    result = tchildren.length();
1169 
1170    if(includeSubNodes)
1171    {
1172    // ...
1173    }
1174 
1175    return result;
1176    }
1177     */
1178 
1179    version (DFL_NO_IMAGELIST) {
1180    } else {
1181 
1182       final @property void imageList(ImageList imglist) {
1183          if (isHandleCreated) {
1184             prevwproc(TVM_SETIMAGELIST, TVSIL_NORMAL,
1185                cast(LPARAM)(imglist ? imglist.handle : cast(HIMAGELIST) null));
1186          }
1187 
1188          _imglist = imglist;
1189       }
1190 
1191       final @property ImageList imageList() {
1192          return _imglist;
1193       }
1194 
1195       /*
1196 
1197       // Default image index (if -1 use this).
1198       final @property void imageIndex(int index)
1199       {
1200       _defimgidx = index;
1201       }
1202 
1203 
1204       final @property int imageIndex()
1205       {
1206       return _defimgidx;
1207       }
1208        */
1209 
1210       final @property void selectedImageIndex(int index) {
1211          //assert(index >= 0);
1212          assert(index >= -1);
1213          _selimgidx = index;
1214 
1215          if (isHandleCreated) {
1216             TreeNode curnode = selectedNode;
1217             _crecreate();
1218             if (curnode) {
1219                curnode.ensureVisible();
1220             }
1221          }
1222       }
1223 
1224       final @property int selectedImageIndex() {
1225          return _selimgidx;
1226       }
1227    }
1228 
1229    protected override @property Size defaultSize() {
1230       return Size(120, 100);
1231    }
1232 
1233    /*
1234       override void createHandle()
1235       {
1236       if(isHandleCreated)
1237       return;
1238 
1239       createClassHandle(TREEVIEW_CLASSNAME);
1240 
1241       onHandleCreated(EventArgs.empty);
1242       }
1243     */
1244 
1245    protected override void createParams(ref CreateParams cp) {
1246       super.createParams(cp);
1247 
1248       cp.className = TREEVIEW_CLASSNAME;
1249    }
1250 
1251    protected override void onHandleCreated(EventArgs ea) {
1252       super.onHandleCreated(ea);
1253 
1254       prevwproc(CCM_SETVERSION, 5, 0); // Fixes font size issue.
1255 
1256       prevwproc(TVM_SETINDENT, ind, 0);
1257 
1258       prevwproc(TVM_SETITEMHEIGHT, iheight, 0);
1259 
1260       version (DFL_NO_IMAGELIST) {
1261       } else {
1262          if (_imglist) {
1263             prevwproc(TVM_SETIMAGELIST, TVSIL_NORMAL, cast(LPARAM) _imglist.handle);
1264          }
1265       }
1266 
1267       tchildren.doNodes();
1268    }
1269 
1270    protected override void onHandleDestroyed(EventArgs ea) {
1271       tchildren._resetHandles();
1272 
1273       super.onHandleDestroyed(ea);
1274    }
1275 
1276    protected override void wndProc(ref Message m) {
1277       // TODO: support these messages.
1278       switch (m.msg) {
1279       case TVM_INSERTITEMA:
1280       case TVM_INSERTITEMW:
1281          m.result = cast(LRESULT) 0;
1282          return;
1283 
1284       case TVM_SETITEMA:
1285       case TVM_SETITEMW:
1286          m.result = cast(LRESULT)-1;
1287          return;
1288 
1289       case TVM_DELETEITEM:
1290          m.result = FALSE;
1291          return;
1292 
1293       case TVM_SETIMAGELIST:
1294          m.result = cast(LRESULT) 0;
1295          return;
1296 
1297       default:
1298       }
1299 
1300       super.wndProc(m);
1301    }
1302 
1303    protected override void prevWndProc(ref Message msg) {
1304       //msg.result = CallWindowProcA(treeviewPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1305       msg.result = dfl.internal.utf.callWindowProc(treeviewPrevWndProc,
1306          msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1307    }
1308 
1309    //TreeViewEventHandler afterCollapse;
1310    Event!(TreeView, TreeViewEventArgs) afterCollapse;
1311    //TreeViewEventHandler afterExpand;
1312    Event!(TreeView, TreeViewEventArgs) afterExpand;
1313    //TreeViewEventHandler afterSelect;
1314    Event!(TreeView, TreeViewEventArgs) afterSelect;
1315    //NodeLabelEditEventHandler afterLabelEdit;
1316    Event!(TreeView, NodeLabelEditEventArgs) afterLabelEdit;
1317    //TreeViewCancelEventHandler beforeCollapse;
1318    Event!(TreeView, TreeViewCancelEventArgs) beforeCollapse;
1319    //TreeViewCancelEventHandler beforeExpand;
1320    Event!(TreeView, TreeViewCancelEventArgs) beforeExpand;
1321    //TreeViewCancelEventHandler beforeSelect;
1322    Event!(TreeView, TreeViewCancelEventArgs) beforeSelect;
1323    //NodeLabelEditEventHandler beforeLabelEdit;
1324    Event!(TreeView, NodeLabelEditEventArgs) beforeLabelEdit;
1325 
1326    protected void onAfterCollapse(TreeViewEventArgs ea) {
1327       afterCollapse(this, ea);
1328    }
1329 
1330    protected void onAfterExpand(TreeViewEventArgs ea) {
1331       afterExpand(this, ea);
1332    }
1333 
1334    protected void onAfterSelect(TreeViewEventArgs ea) {
1335       afterSelect(this, ea);
1336    }
1337 
1338    protected void onAfterLabelEdit(NodeLabelEditEventArgs ea) {
1339       afterLabelEdit(this, ea);
1340    }
1341 
1342    protected void onBeforeCollapse(TreeViewCancelEventArgs ea) {
1343       beforeCollapse(this, ea);
1344    }
1345 
1346    protected void onBeforeExpand(TreeViewCancelEventArgs ea) {
1347       beforeExpand(this, ea);
1348    }
1349 
1350    protected void onBeforeSelect(TreeViewCancelEventArgs ea) {
1351       beforeSelect(this, ea);
1352    }
1353 
1354    protected void onBeforeLabelEdit(NodeLabelEditEventArgs ea) {
1355       beforeLabelEdit(this, ea);
1356    }
1357 
1358    protected override void onReflectedMessage(ref Message m) { // package
1359       super.onReflectedMessage(m);
1360 
1361       switch (m.msg) {
1362       case WM_NOTIFY: {
1363             NMHDR* nmh;
1364             NM_TREEVIEW* nmtv;
1365             TreeViewCancelEventArgs cea;
1366 
1367             nmh = cast(NMHDR*) m.lParam;
1368             assert(nmh.hwndFrom == hwnd);
1369 
1370             switch (nmh.code) {
1371             case NM_CUSTOMDRAW: {
1372                   NMTVCUSTOMDRAW* tvcd;
1373                   tvcd = cast(NMTVCUSTOMDRAW*) nmh;
1374                   //if(tvcd.nmcd.dwDrawStage & CDDS_ITEM)
1375                   {
1376                      //if(tvcd.nmcd.uItemState & CDIS_SELECTED)
1377                      if ((tvcd.nmcd.dwDrawStage & CDDS_ITEM)
1378                            && (tvcd.nmcd.uItemState & CDIS_SELECTED)) {
1379                         // Note: might not look good with custom colors.
1380                         tvcd.clrText = SystemColors.highlightText.toRgb();
1381                         tvcd.clrTextBk = SystemColors.highlight.toRgb();
1382                      } else {
1383                         //tvcd.clrText = foreColor.toRgb();
1384                         tvcd.clrText = foreColor.solidColor(backColor).toRgb();
1385                         tvcd.clrTextBk = backColor.toRgb();
1386                      }
1387                   }
1388                   m.result |= CDRF_NOTIFYITEMDRAW; // | CDRF_NOTIFYITEMERASE;
1389 
1390                   // This doesn't seem to be doing anything.
1391                   Font fon;
1392                   fon = this.font;
1393                   if (fon) {
1394                      SelectObject(tvcd.nmcd.hdc, fon.handle);
1395                      m.result |= CDRF_NEWFONT;
1396                   }
1397                }
1398                break;
1399 
1400                /*
1401                      case TVN_GETDISPINFOA:
1402 
1403                      break;
1404                    */
1405 
1406             case TVN_SELCHANGINGW:
1407                goto sel_changing;
1408 
1409             case TVN_SELCHANGINGA:
1410                if (dfl.internal.utf.useUnicode) {
1411                   break;
1412                }
1413             sel_changing:
1414 
1415                nmtv = cast(NM_TREEVIEW*) nmh;
1416                switch (nmtv.action) {
1417                case TVC_BYMOUSE:
1418                   cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1419                      false, TreeViewAction.BY_MOUSE);
1420                   onBeforeSelect(cea);
1421                   m.result = cea.cancel;
1422                   break;
1423 
1424                case TVC_BYKEYBOARD:
1425                   cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1426                      false, TreeViewAction.BY_KEYBOARD);
1427                   onBeforeSelect(cea);
1428                   m.result = cea.cancel;
1429                   break;
1430 
1431                   //case TVC_UNKNOWN:
1432                default:
1433                   cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1434                      false, TreeViewAction.UNKNOWN);
1435                   onBeforeSelect(cea);
1436                   m.result = cea.cancel;
1437                }
1438                break;
1439 
1440             case TVN_SELCHANGEDW:
1441                goto sel_changed;
1442 
1443             case TVN_SELCHANGEDA:
1444                if (dfl.internal.utf.useUnicode) {
1445                   break;
1446                }
1447             sel_changed:
1448 
1449                nmtv = cast(NM_TREEVIEW*) nmh;
1450                switch (nmtv.action) {
1451                case TVC_BYMOUSE:
1452                   onAfterSelect(new TreeViewEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1453                      TreeViewAction.BY_MOUSE));
1454                   break;
1455 
1456                case TVC_BYKEYBOARD:
1457                   onAfterSelect(new TreeViewEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1458                      TreeViewAction.BY_KEYBOARD));
1459                   break;
1460 
1461                   //case TVC_UNKNOWN:
1462                default:
1463                   onAfterSelect(new TreeViewEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1464                      TreeViewAction.UNKNOWN));
1465                }
1466                break;
1467 
1468             case TVN_ITEMEXPANDINGW:
1469                goto item_expanding;
1470 
1471             case TVN_ITEMEXPANDINGA:
1472                if (dfl.internal.utf.useUnicode) {
1473                   break;
1474                }
1475             item_expanding:
1476 
1477                nmtv = cast(NM_TREEVIEW*) nmh;
1478                switch (nmtv.action) {
1479                case TVE_COLLAPSE:
1480                   cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1481                      false, TreeViewAction.COLLAPSE);
1482                   onBeforeCollapse(cea);
1483                   m.result = cea.cancel;
1484                   break;
1485 
1486                case TVE_EXPAND:
1487                   cea = new TreeViewCancelEventArgs(cast(TreeNode) cast(void*) nmtv.itemNew.lParam,
1488                      false, TreeViewAction.EXPAND);
1489                   onBeforeExpand(cea);
1490                   m.result = cea.cancel;
1491                   break;
1492 
1493                default:
1494                }
1495                break;
1496 
1497             case TVN_ITEMEXPANDEDW:
1498                goto item_expanded;
1499 
1500             case TVN_ITEMEXPANDEDA:
1501                if (dfl.internal.utf.useUnicode) {
1502                   break;
1503                }
1504             item_expanded:
1505 
1506                nmtv = cast(NM_TREEVIEW*) nmh;
1507                switch (nmtv.action) {
1508                case TVE_COLLAPSE: {
1509                      scope TreeViewEventArgs tvea = new TreeViewEventArgs(
1510                         cast(TreeNode) cast(void*) nmtv.itemNew.lParam, TreeViewAction.COLLAPSE);
1511                      onAfterCollapse(tvea);
1512                   }
1513                   break;
1514 
1515                case TVE_EXPAND: {
1516                      scope TreeViewEventArgs tvea = new TreeViewEventArgs(
1517                         cast(TreeNode) cast(void*) nmtv.itemNew.lParam, TreeViewAction.EXPAND);
1518                      onAfterExpand(tvea);
1519                   }
1520                   break;
1521 
1522                default:
1523                }
1524                break;
1525 
1526             case TVN_BEGINLABELEDITW:
1527                goto begin_label_edit;
1528 
1529             case TVN_BEGINLABELEDITA:
1530                if (dfl.internal.utf.useUnicode) {
1531                   break;
1532                }
1533             begin_label_edit: {
1534                   TV_DISPINFOA* nmdi;
1535                   nmdi = cast(TV_DISPINFOA*) nmh;
1536                   TreeNode node;
1537                   node = cast(TreeNode) cast(void*) nmdi.item.lParam;
1538                   scope NodeLabelEditEventArgs nleea = new NodeLabelEditEventArgs(node);
1539                   onBeforeLabelEdit(nleea);
1540                   m.result = nleea.cancelEdit;
1541                }
1542                break;
1543 
1544             case TVN_ENDLABELEDITW: {
1545                   Dstring label;
1546                   TV_DISPINFOW* nmdi;
1547                   nmdi = cast(TV_DISPINFOW*) nmh;
1548                   if (nmdi.item.pszText) {
1549                      TreeNode node;
1550                      node = cast(TreeNode) cast(void*) nmdi.item.lParam;
1551                      label = fromUnicodez(nmdi.item.pszText);
1552                      scope NodeLabelEditEventArgs nleea = new NodeLabelEditEventArgs(node,
1553                         label);
1554                      onAfterLabelEdit(nleea);
1555                      if (nleea.cancelEdit) {
1556                         m.result = FALSE;
1557                      } else {
1558                         // TODO: check if correct implementation.
1559                         // Update the node's cached text..
1560                         node.ttext = label;
1561 
1562                         m.result = TRUE;
1563                      }
1564                   }
1565                }
1566                break;
1567 
1568             case TVN_ENDLABELEDITA:
1569                if (dfl.internal.utf.useUnicode) {
1570                   break;
1571                } else {
1572                   Dstring label;
1573                   TV_DISPINFOA* nmdi;
1574                   nmdi = cast(TV_DISPINFOA*) nmh;
1575                   if (nmdi.item.pszText) {
1576                      TreeNode node;
1577                      node = cast(TreeNode) cast(void*) nmdi.item.lParam;
1578                      label = fromAnsiz(nmdi.item.pszText);
1579                      scope NodeLabelEditEventArgs nleea = new NodeLabelEditEventArgs(node,
1580                         label);
1581                      onAfterLabelEdit(nleea);
1582                      if (nleea.cancelEdit) {
1583                         m.result = FALSE;
1584                      } else {
1585                         // TODO: check if correct implementation.
1586                         // Update the node's cached text..
1587                         node.ttext = label;
1588 
1589                         m.result = TRUE;
1590                      }
1591                   }
1592                   break;
1593                }
1594 
1595             default:
1596             }
1597          }
1598          break;
1599 
1600       default:
1601       }
1602    }
1603 
1604 private:
1605    TreeNodeCollection tchildren;
1606    int ind = 19; // Indent.
1607    dchar pathsep = '\\';
1608    bool _sort = false;
1609    int iheight = 16;
1610    version (DFL_NO_IMAGELIST) {
1611    } else {
1612       ImageList _imglist;
1613       int _selimgidx = -1; //0;
1614    }
1615 
1616    TreeNode treeNodeFromHandle(HTREEITEM hnode) {
1617       TV_ITEMA ti;
1618       ti.mask = TVIF_HANDLE | TVIF_PARAM;
1619       ti.hItem = hnode;
1620       if (SendMessageA(hwnd, TVM_GETITEMA, 0, cast(LPARAM)&ti)) {
1621          return cast(TreeNode) cast(void*) ti.lParam;
1622       }
1623       return null;
1624    }
1625 
1626 package:
1627 final:
1628    LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam) {
1629       //return CallWindowProcA(treeviewPrevWndProc, hwnd, msg, wparam, lparam);
1630       return dfl.internal.utf.callWindowProc(treeviewPrevWndProc, hwnd, msg, wparam,
1631          lparam);
1632    }
1633 }