1 /**
2  * Widget module.
3  *
4  * License:
5  *     MIT. See LICENSE for full details.
6  */
7 module tkd.widget.treeview;
8 
9 /**
10  * Imports.
11  */
12 import std.algorithm;
13 import std.array;
14 import std.conv;
15 import std.regex;
16 import std..string;
17 import tkd.element.color;
18 import tkd.element.element;
19 import tkd.element.uielement;
20 import tkd.image.image;
21 import tkd.widget.anchorposition;
22 import tkd.widget.common.height;
23 import tkd.widget.common.padding;
24 import tkd.widget.common.xscrollcommand;
25 import tkd.widget.common.yscrollcommand;
26 import tkd.widget.widget;
27 
28 /**
29  * The treeview widget displays a hierarchical collection of items. Each item 
30  * has a textual label, an optional image, and an optional list of data values. 
31  *
32  * There are two varieties of columns. The first is the main tree view column 
33  * that is present all the time. The second are data columns that can be added 
34  * when needed.
35  *
36  * Each tree item has a list of tags, which can be used to associate event 
37  * bindings and control their appearance. Treeview widgets support horizontal 
38  * and vertical scrolling with the standard scroll commands.
39  *
40  * Example:
41  * ---
42  * auto treeView = new TreeView()
43  * 	.setHeading("Text")
44  * 	.addRow(new TreeViewRow(["row1"]))
45  * 	.addRow(new TreeViewRow(["row2"]))
46  * 	.pack();
47  * ---
48  *
49  * Common_Commands:
50  *     These are injected common commands that can also be used with this widget.
51  *     $(P
52  *         $(LINK2 ./common/height.html, Height) $(BR)
53  *         $(LINK2 ./common/padding.html, Padding) $(BR)
54  *         $(LINK2 ./common/xscrollcommand.html, XScrollCommand) $(BR)
55  *         $(LINK2 ./common/yscrollcommand.html, YScrollCommand) $(BR)
56  *     )
57  *
58  * Additional_Events:
59  *     Additional events that can also be bound to using the $(LINK2 ../element/uielement.html#UiElement.bind, bind) method.
60  *     $(P
61  *         <<PrevWindow>>,
62  *         <<TreeviewClose>>
63  *         <<TreeviewOpen>>
64  *         <<TreeviewSelect>>
65  *         <Alt-Key>,
66  *         <B1-Leave>,
67  *         <B1-Motion>,
68  *         <Button-1>,
69  *         <Button-4>,
70  *         <Button-5>,
71  *         <ButtonRelease-1>,
72  *         <Control-Button-1>,
73  *         <Double-Button-1>,
74  *         <Key-Down>,
75  *         <Key-F10>,
76  *         <Key-Left>,
77  *         <Key-Next>,
78  *         <Key-Prior>,
79  *         <Key-Return>,
80  *         <Key-Right>,
81  *         <Key-Tab>,
82  *         <Key-Up>,
83  *         <Key-space>,
84  *         <Leave>,
85  *         <Motion>,
86  *         <Shift-Button-1>,
87  *         <Shift-Button-4>,
88  *         <Shift-Button-5>,
89  *     )
90  *
91  * See_Also:
92  *     $(LINK2 ./widget.html, tkd.widget.widget)
93  */
94 class TreeView : Widget, IXScrollable!(TreeView), IYScrollable!(TreeView)
95 {
96 	/**
97 	 * An array containing all the columns.
98 	 */
99 	private TreeViewColumn[] _columns;
100 
101 	/**
102 	 * Construct the widget.
103 	 *
104 	 * Params:
105 	 *     parent = The parent of this widget.
106 	 *
107 	 * See_Also:
108 	 *     $(LINK2 ../element/uielement.html, tkd.element.uielement) $(BR)
109 	 */
110 	this(UiElement parent = null)
111 	{
112 		super(parent);
113 		this._elementId = "treeview";
114 
115 		this._tk.eval("ttk::treeview %s -selectmode browse", this.id);
116 
117 		// Add the treeview column to the column collection.
118 		this._columns ~= new TreeViewColumn();
119 		this._columns[0].init(this);
120 	}
121 
122 	/**
123 	 * Get the column identifiers of the passed data column indexes.
124 	 *
125 	 * Params:
126 	 *     indexex = The indexes of the data columns.
127 	 *
128 	 * Returns:
129 	 *     A string array containing the columns relating to the indexes.
130 	 */
131 	private string[] getDataColumnIdentifiers(int[] indexes)
132 	{
133 		string[] columns;
134 
135 		for (int x = 1; x < this._columns.length; x++)
136 		{
137 			if (indexes.canFind(x))
138 			{
139 				columns ~= this._columns[x].id;
140 			}
141 		}
142 
143 		return columns;
144 	}
145 
146 	/**
147 	 * Get all column identifiers.
148 	 *
149 	 * Returns:
150 	 *     A string array containing all column identifiers.
151 	 */
152 	private string[] getDataColumnIdentifiers()
153 	{
154 		string[] columns;
155 
156 		for (int x = 1; x < this._columns.length; x++)
157 		{
158 			columns ~= this._columns[x].id;
159 		}
160 
161 		return columns;
162 	}
163 
164 	/**
165 	 * Get the tree view elements that are currently being shown.
166 	 *
167 	 * Returns:
168 	 *     An array cotaining all shown elements.
169 	 */
170 	private string[] getShownElements()
171 	{
172 		this._tk.eval("%s cget -show", this.id);
173 		return this._tk.getResult!(string).split();
174 	}
175 
176 	/**
177 	 * Build the columns found in the column array. This is needed because the 
178 	 * data columns always seem to forget setting if configured piece-meal.
179 	 */
180 	private void buildColumns()
181 	{
182 		// String concatenation is used to build the script here instead of 
183 		// using format specifiers to enable supporting input which includes 
184 		// Tcl/Tk reserved characters and elements that could be construed as 
185 		// format specifiers.
186 		string columns = format(`[list "%s"]`, this.getDataColumnIdentifiers().join(`" "`));
187 		string script  = std.conv.text(this.id, ` configure -columns `, columns);
188 		this._tk.eval(script);
189 
190 		for (int x = 1; x < this._columns.length; x++)
191 		{
192 			this.columns[x].init(this);
193 		}
194 	}
195 
196 	/**
197 	 * Convenience method to set the tree column heading text.
198 	 *
199 	 * Params:
200 	 *    title = The title of the column.
201 	 *    anchor = The anchor position of the text.
202 	 *
203 	 * Returns:
204 	 *     This widget to aid method chaining.
205 	 *
206 	 * See_Also:
207 	 *     $(LINK2 ./anchorposition.html, tkd.widget.anchorposition) $(BR)
208 	 */
209 	public auto setHeading(this T)(string title, string anchor = AnchorPosition.west)
210 	{
211 		this._columns[0].setHeading(title, anchor);
212 
213 		return cast(T) this;
214 	}
215 
216 	/**
217 	 * Convenience method to set the tree column heading image.
218 	 *
219 	 * Params:
220 	 *    image = The image to use.
221 	 *
222 	 * Returns:
223 	 *     This widget to aid method chaining.
224 	 */
225 	public auto setHeadingImage(this T)(Image image)
226 	{
227 		this._columns[0].setHeadingImage(image);
228 
229 		return cast(T) this;
230 	}
231 
232 	/**
233 	 * Convenience method to set the tree column command to be executed when 
234 	 * clicking on the heading.
235 	 *
236 	 * Params:
237 	 *    callback = The delegate callback to execute when invoking the command.
238 	 *
239 	 * Returns:
240 	 *     This widget to aid method chaining.
241 	 *
242 	 * Callback_Arguments:
243 	 *     These are the fields within the callback's $(LINK2 
244 	 *     ../element/element.html#CommandArgs, CommandArgs) parameter which 
245 	 *     are populated by this method when the callback is executed. 
246 	 *     $(P
247 	 *         $(PARAM_TABLE
248 	 *             $(PARAM_ROW CommandArgs.element, The tree column.)
249 	 *             $(PARAM_ROW CommandArgs.uniqueData, The internal id of the tree view (An implementation detail that's not very useful).)
250 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
251 	 *         )
252 	 *     )
253 	 *
254 	 * See_Also:
255 	 *     $(LINK2 ../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
256 	 */
257 	public auto setHeadingCommand(this T)(CommandCallback callback)
258 	{
259 		this._columns[0].setHeadingCommand(callback);
260 
261 		return cast(T) this;
262 	}
263 
264 	/**
265 	 * Convenience method to remove the tree view column command.
266 	 *
267 	 * Returns:
268 	 *     This widget to aid method chaining.
269 	 */
270 	public auto removeHeadingCommand(this T)()
271 	{
272 		this._columns[0].removeHeadingCommand();
273 
274 		return cast(T) this;
275 	}
276 
277 	/**
278 	 * Convenience method to set the minium width of the tree column.
279 	 *
280 	 * Params:
281 	 *     minWidth = The minimum width in pixels.
282 	 *
283 	 * Returns:
284 	 *     This widget to aid method chaining.
285 	 */
286 	public auto setMinWidth(this T)(int minWidth)
287 	{
288 		this._columns[0].setMinWidth(minWidth);
289 
290 		return cast(T) this;
291 	}
292 
293 	/**
294 	 * Convenience method to enable or disable stretching for the tree column. 
295 	 * This controls how this column react when other columns or the parent 
296 	 * widget is resized.
297 	 *
298 	 * Params:
299 	 *     stretch = true for enabling stretching, false to disable.
300 	 *
301 	 * Returns:
302 	 *     This widget to aid method chaining.
303 	 */
304 	public auto setStretch(this T)(bool stretch)
305 	{
306 		this._columns[0].setStretch(stretch);
307 
308 		return cast(T) this;
309 	}
310 
311 	/**
312 	 * Convenience method to set the width of the tree column.
313 	 *
314 	 * Params:
315 	 *     width = The width in pixels.
316 	 *
317 	 * Returns:
318 	 *     This widget to aid method chaining.
319 	 */
320 	public auto setWidth(this T)(int width)
321 	{
322 		this._columns[0].setWidth(width);
323 
324 		return cast(T) this;
325 	}
326 
327 	/**
328 	 * Add a new column to the tree view.
329 	 *
330 	 * Params:
331 	 *     column = The new column to add.
332 	 *
333 	 * Returns:
334 	 *     This widget to aid method chaining.
335 	 */
336 	public auto addColumn(this T)(TreeViewColumn column)
337 	{
338 		this._columns ~= column;
339 		this.buildColumns();
340 
341 		return cast(T) this;
342 	}
343 
344 	/**
345 	 * Add a row to the tree view.
346 	 *
347 	 * Params:
348 	 *     row = The row to add.
349 	 *
350 	 * Returns:
351 	 *     This widget to aid method chaining.
352 	 */
353 	public auto addRow(this T)(TreeViewRow row)
354 	{
355 		this.appendRows("", [row]);
356 
357 		return cast(T) this;
358 	}
359 
360 	/**
361 	 * Add an array of rowr to the tree view.
362 	 *
363 	 * Params:
364 	 *     rows = The rows to add.
365 	 *
366 	 * Returns:
367 	 *     This widget to aid method chaining.
368 	 */
369 	public auto addRows(this T)(TreeViewRow[] rows)
370 	{
371 		this.appendRows("", rows);
372 
373 		return cast(T) this;
374 	}
375 
376 	/**
377 	 * This method does the actualy work of adding rows to the tree view.
378 	 * All children are recursed and added too.
379 	 *
380 	 * Params:
381 	 *     parentRow = The id of the parent row. Use '{}' for the top level.
382 	 *     rows = The rows to add to the tree view.
383 	 */
384 	private void appendRows(string parentRow, TreeViewRow[] rows)
385 	{
386 		string values;
387 		string tags;
388 
389 		foreach (row; rows)
390 		{
391 			row._values = this._tk.escape(row._values);
392 
393 			if (row.values.length > 1)
394 			{
395 				// Build the data column list.
396 				values = " -values " ~ format(`[list "%s"]`, row.values[1 .. $].join(`" "`));
397 			}
398 
399 			row._tags = this._tk.escape(row._tags);
400 
401 			if (row.tags.length)
402 			{
403 				// Build the tags list.
404 				tags = " -tags " ~ format(`[list "%s"]`, row.tags.join(`" "`));
405 			}
406 
407 			// String concatenation is used to build the script here instead of 
408 			// using format specifiers to enable supporting input which includes 
409 			// Tcl/Tk reserved characters and elements that could be construed as 
410 			// format specifiers.
411 			string script = std.conv.text(this.id, ` insert {`, parentRow, `} end -text "`, row.values[0], `"`, values, ` -open `, row.isOpen, tags);
412 			this._tk.eval(script);
413 
414 			row._rowId = this._tk.getResult!(string);
415 
416 			if (row.children.length)
417 			{
418 				this.appendRows(row.id, row.children);
419 			}
420 		}
421 	}
422 
423 	/**
424 	 * Set the value of a data column, given a row id. Row id's are populated 
425 	 * within a tree view row object once that row has been inserted into the 
426 	 * tree view.
427 	 *
428 	 * Params:
429 	 *     rowId = The row id.
430 	 *     columnIndex = The 0-based data column index.
431 	 *     value = The new value.
432 	 *
433 	 * Returns:
434 	 *     This widget to aid method chaining.
435 	 */
436 	public auto updateDataColumn(this T)(string rowId, uint columnIndex, string value)
437 	{
438 		// String concatenation is used to build the script here instead of 
439 		// using format specifiers to enable supporting input which includes 
440 		// Tcl/Tk reserved characters and elements that could be construed as 
441 		// format specifiers.
442 		string script = std.conv.text(this.id, ` set `, rowId, ` `, columnIndex, ` "`, this._tk.escape(value), `"`);
443 		this._tk.eval(script);
444 
445 		return cast(T) this;
446 	}
447 
448 	/**
449 	 * Set image and colors for a specific tag.
450 	 * Use colors from the preset color $(LINK2 ../element/color.html, list) or a web style hex color.
451 	 *
452 	 * Params:
453 	 *     name = The name of the tag.
454 	 *     image = The image to associate to the tag.
455 	 *     foreground = The forground color.
456 	 *     background = The background color.
457 	 *
458 	 * Returns:
459 	 *     This widget to aid method chaining.
460 	 *
461 	 * See_Also:
462 	 *     $(LINK2 ../element/color.html, tkd.widget.color) $(BR)
463 	 */
464 	public auto setTag(this T)(string name, Image image, string foreground = Color.default_, string background = Color.default_)
465 	{
466 		this._tk.eval("%s tag configure %s -image %s -foreground {%s} -background {%s}", this.id, name, image.id, foreground, background);
467 
468 		return cast(T) this;
469 	}
470 
471 	/**
472 	 * Get the columns.
473 	 *
474 	 * Returns:
475 	 *     An array containing all the data columns.
476 	 */
477 	public @property TreeViewColumn[] columns()
478 	{
479 		return this._columns;
480 	}
481 
482 	/**
483 	 * Show all data columns in the event some or all are hidden.
484 	 *
485 	 * Returns:
486 	 *     This widget to aid method chaining.
487 	 */
488 	public auto displayAllDataColumns(this T)()
489 	{
490 		this._tk.eval("%s configure -displaycolumns #all", this.id);
491 
492 		return cast(T) this;
493 	}
494 
495 	/**
496 	 * Show the data columns that relate to the indexes passed.
497 	 *
498 	 * Params:
499 	 *     indexes = The indexes of the data columns to show.
500 	 *
501 	 * Returns:
502 	 *     This widget to aid method chaining.
503 	 */
504 	public auto displayDataColumns(this T)(int[] indexes)
505 	{
506 		string columns = format(`[list "%s"]`, this.getDataColumnIdentifiers(indexes).join(`" "`));
507 
508 		// String concatenation is used to build the script here instead of 
509 		// using format specifiers to enable supporting input which includes 
510 		// Tcl/Tk reserved characters and elements that could be construed as 
511 		// format specifiers.
512 		string script = std.conv.text(this.id, ` configure -displaycolumns `, columns);
513 		this._tk.eval(script);
514 
515 		return cast(T) this;
516 	}
517 
518 	/**
519 	 * Set the selection mode.
520 	 *
521 	 * Params:
522 	 *     mode = The mode of the selection.
523 	 *
524 	 * Returns:
525 	 *     This widget to aid method chaining.
526 	 *
527 	 * See_Also:
528 	 *     $(LINK2 ./treeview.html#TreeViewSelectionMode, tkd.widget.treeview.TreeViewSelectionMode) $(BR)
529 	 */
530 	public auto setSelectionMode(this T)(string mode)
531 	{
532 		this._tk.eval("%s configure -selectmode %s", this.id, mode);
533 
534 		return cast(T) this;
535 	}
536 
537 	/**
538 	 * Hide the headings from all columns.
539 	 *
540 	 * Returns:
541 	 *     This widget to aid method chaining.
542 	 */
543 	public auto hideHeadings(this T)()
544 	{
545 		this._tk.eval("%s configure -show { %s }", this.id, this.getShownElements()
546 			.remove!(x => x == "headings")
547 			.join(" ")
548 		);
549 
550 		return cast(T) this;
551 	}
552 
553 	/**
554 	 * Show the headings for all columns.
555 	 *
556 	 * Returns:
557 	 *     This widget to aid method chaining.
558 	 */
559 	public auto showHeadings(this T)()
560 	{
561 		string[] elements = this.getShownElements();
562 		elements ~= "headings";
563 
564 		this._tk.eval("%s configure -show { %s }", this.id, elements.join(" "));
565 
566 		return cast(T) this;
567 	}
568 
569 	/**
570 	 * Hide the tree view column.
571 	 *
572 	 * Returns:
573 	 *     This widget to aid method chaining.
574 	 */
575 	public auto hideTreeColumn(this T)()
576 	{
577 		this._tk.eval("%s configure -show { %s }", this.id, this.getShownElements()
578 			.remove!(x => x == "tree")
579 			.join(" ")
580 		);
581 
582 		return cast(T) this;
583 	}
584 
585 	/**
586 	 * Show the tree view column.
587 	 *
588 	 * Returns:
589 	 *     This widget to aid method chaining.
590 	 */
591 	public auto showTreeColumn(this T)()
592 	{
593 		string[] elements = this.getShownElements();
594 		elements ~= "tree";
595 
596 		this._tk.eval("%s configure -show { %s }", this.id, elements.join(" "));
597 
598 		return cast(T) this;
599 	}
600 
601 	/**
602 	 * Construct a row object from a row id.
603 	 *
604 	 * Params:
605 	 *     rowId = The id of the row to construct.
606 	 *
607 	 * Returns:
608 	 *     A tree view row.
609 	 */
610 	private TreeViewRow getRowFromId(string rowId)
611 	{
612 		auto row = new TreeViewRow();
613 
614 		this._tk.eval("%s item %s -text", this.id, rowId);
615 		row._values ~= this._tk.getResult!(string);
616 
617 		this._tk.eval("%s item %s -values", this.id, rowId);
618 		auto results = matchAll(this._tk.getResult!(string), "\"(.*?)\"");
619 		foreach (result; results)
620 		{
621 			row._values ~= result.captures[1];
622 		}
623 
624 		this._tk.eval("%s item %s -open", this.id, rowId);
625 		row._isOpen = this._tk.getResult!(bool);
626 
627 		this._tk.eval("%s item %s -tags", this.id, rowId);
628 		results = matchAll(this._tk.getResult!(string), "\"(.*?)\"");
629 		foreach (result; results)
630 		{
631 			row._tags ~= result.captures[1];
632 		}
633 
634 		return row;
635 	}
636 
637 	/**
638 	 * Populate row objects and return them.
639 	 *
640 	 * Params:
641 	 *     rows = An array to append the rows to.
642 	 *     includeChildren = Specifies whether or not to include the children.
643 	 *
644 	 * Returns:
645 	 *     AN array of tree view rows.
646 	 */
647 	private TreeViewRow[] populateRows(string[] rowIds, bool includeChildren)
648 	{
649 		TreeViewRow[] rows;
650 		TreeViewRow currentRow;
651 
652 		foreach (rowId; rowIds)
653 		{
654 			currentRow = this.getRowFromId(rowId);
655 
656 			if (includeChildren)
657 			{
658 				this._tk.eval("%s children %s", this.id, rowId);
659 				currentRow.children = this.populateRows(this._tk.getResult!(string).split(), includeChildren);
660 			}
661 
662 			rows ~= currentRow;
663 		}
664 
665 		return rows;
666 	}
667 
668 	/**
669 	 * Get the row(s) selected in the tree view.
670 	 *
671 	 * Params:
672 	 *     includeChildren = Specifies whether or not to include the children.
673 	 *
674 	 * Returns:
675 	 *     An array containing the selected rows.
676 	 */
677 	public TreeViewRow[] getSelectedRows(bool includeChildren = false)
678 	{
679 		this._tk.eval("%s selection", this.id);
680 		string[] rowIds = this._tk.getResult!(string).split();
681 
682 		return this.populateRows(rowIds, includeChildren);
683 	}
684 
685 	/**
686 	 * Delete all rows in the widget.
687 	 *
688 	 * Returns:
689 	 *     This widget to aid method chaining.
690 	 */
691 	public auto deleteRows()
692 	{
693 		this._tk.eval("%s children {}", this.id);
694 		this._tk.eval("%s delete {%s}", this.id, this._tk.getResult!(string));
695 	}
696 
697 	/**
698 	 * Mixin common commands.
699 	 */
700 	mixin Height;
701 	mixin Padding;
702 	mixin XScrollCommand!(TreeView);
703 	mixin YScrollCommand!(TreeView);
704 }
705 
706 /**
707  * A class representing a column in the tree view.
708  */
709 class TreeViewColumn : Element
710 {
711 	/**
712 	 * The parent of this column.
713 	 */
714 	private TreeView _parent;
715 
716 	/**
717 	 * The title of the heading.
718 	 */
719 	private string _title;
720 
721 	/**
722 	 * The anchor position of the heading title.
723 	 */
724 	private string _anchor = AnchorPosition.west;
725 
726 	/**
727 	 * The image of the heading.
728 	 */
729 	private Image _image;
730 
731 	/**
732 	 * The minimum width of the column.
733 	 */
734 	private int _minWidth = 20;
735 
736 	/**
737 	 * Whether to alter the size of the column when the widget is resized.
738 	 */
739 	private bool _stretch = true;
740 
741 	/**
742 	 * Width of the column.
743 	 */
744 	private int _width = 200;
745 
746 	/**
747 	 * The command associated with the heading.
748 	 */
749 	private CommandCallback _commandCallback;
750 
751 	/**
752 	 * Construct a new column to add the treeview column to the column 
753 	 * collection.
754 	 *
755 	 * '#0' is the built-in Tcl/Tk display id of the tree view column.
756 	 * This allows access to this column even if it wasn't created by us.
757 	 */
758 	private this()
759 	{
760 		super();
761 		this.overrideGeneratedId("#0");
762 	}
763 
764 	/**
765 	 * Construct a new column.
766 	 *
767 	 * Params:
768 	 *     title = The optional title of the heading.
769 	 *     anchor = The anchor position of the heading title.
770 	 */
771 	public this(string title = null, string anchor = AnchorPosition.west)
772 	{
773 		this._elementId = "column";
774 		this.setHeading(title, anchor);
775 	}
776 
777 	/**
778 	 * Initialise the column and attach to a parent.
779 	 *
780 	 * Params:
781 	 *     parent = The parent tree view.
782 	 */
783 	private void init(TreeView parent)
784 	{
785 		this._parent = parent;
786 
787 		this.setHeading(this._title, this._anchor);
788 		this.setHeadingImage(this._image);
789 		this.setHeadingCommand(this._commandCallback);
790 		this.setMinWidth(this._minWidth);
791 		this.setStretch(this._stretch);
792 		this.setWidth(this._width);
793 	}
794 
795 	/**
796 	 * Set the heading title.
797 	 *
798 	 * Params:
799 	 *    title = The title of the column.
800 	 *    anchor = The anchor position of the text.
801 	 *
802 	 * Returns:
803 	 *     This column to aid method chaining.
804 	 *
805 	 * See_Also:
806 	 *     $(LINK2 ./anchorposition.html, tkd.widget.anchorposition) $(BR)
807 	 */
808 	public auto setHeading(this T)(string title, string anchor = AnchorPosition.west)
809 	{
810 		this._title  = title;
811 		this._anchor = anchor;
812 
813 		if (this._parent)
814 		{
815 			// String concatenation is used to build the script here instead of 
816 			// using format specifiers to enable supporting input which includes 
817 			// Tcl/Tk reserved characters and elements that could be construed as 
818 			// format specifiers.
819 			string script = std.conv.text(this._parent.id, ` heading `, this.id, ` -text "`, this._tk.escape(this._title), `" -anchor `, this._anchor);
820 			this._tk.eval(script);
821 
822 		}
823 
824 		return cast(T) this;
825 	}
826 
827 	/**
828 	 * Set the heading image.
829 	 *
830 	 * Params:
831 	 *    image = The image to use.
832 	 *
833 	 * Returns:
834 	 *     This column to aid method chaining.
835 	 */
836 	public auto setHeadingImage(this T)(Image image)
837 	{
838 		this._image = image;
839 
840 		if (this._parent && this._image)
841 		{
842 			// String concatenation is used to build the script here instead of 
843 			// using format specifiers to enable supporting input which includes 
844 			// Tcl/Tk reserved characters and elements that could be construed as 
845 			// format specifiers.
846 			string script = std.conv.text(this._parent.id, ` heading `, this.id, ` -text "`, this._tk.escape(this._title), `" -anchor `, this._anchor, ` -image `, image.id);
847 			this._tk.eval(script);
848 		}
849 
850 		return cast(T) this;
851 	}
852 
853 	/**
854 	 * Set the column command to be executed when clicking on the heading.
855 	 *
856 	 * Params:
857 	 *    callback = The delegate callback to execute when invoking the command.
858 	 *
859 	 * Returns:
860 	 *     This widget to aid method chaining.
861 	 *
862 	 * Callback_Arguments:
863 	 *     These are the fields within the callback's $(LINK2 
864 	 *     ../element/element.html#CommandArgs, CommandArgs) parameter which 
865 	 *     are populated by this method when the callback is executed. 
866 	 *     $(P
867 	 *         $(PARAM_TABLE
868 	 *             $(PARAM_ROW CommandArgs.element, The column that executed the callback.)
869 	 *             $(PARAM_ROW CommandArgs.uniqueData, The internal id of the tree view (An implementation detail that's not very useful).)
870 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
871 	 *         )
872 	 *     )
873 	 *
874 	 * See_Also:
875 	 *     $(LINK2 ../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
876 	 */
877 	public auto setHeadingCommand(this T)(CommandCallback callback)
878 	{
879 		this._commandCallback = callback;
880 
881 		if (this._parent && this._commandCallback)
882 		{
883 			string command = this.createCommand(callback, this._parent.id);
884 			this._tk.eval("%s heading %s -command %s", this._parent.id, this.id, command);
885 		}
886 
887 		return cast(T) this;
888 	}
889 
890 	/**
891 	 * Remove the column command.
892 	 *
893 	 * Returns:
894 	 *     This widget to aid method chaining.
895 	 */
896 	public auto removeHeadingCommand(this T)()
897 	{
898 		if (this._parent && this._commandCallback)
899 		{
900 			this._tk.deleteCommand(this.getCommandName(this._parent.id));
901 			this._tk.eval("%s heading %s -command {}", this._parent.id, this.id);
902 		}
903 
904 		return cast(T) this;
905 	}
906 
907 	/**
908 	 * Set the minium width of the column.
909 	 *
910 	 * Params:
911 	 *     minWidth = The minimum width in pixels.
912 	 *
913 	 * Returns:
914 	 *     This widget to aid method chaining.
915 	 */
916 	public auto setMinWidth(this T)(int minWidth)
917 	{
918 		this._minWidth = minWidth;
919 
920 		if (this._parent)
921 		{
922 			this._tk.eval("%s column %s -minwidth %s", this._parent.id, this.id, this._minWidth);
923 		}
924 
925 		return cast(T) this;
926 	}
927 
928 	/**
929 	 * Enable or disable stretching for the column. This controls how this 
930 	 * column react when other columns or the parent widget is resized.
931 	 *
932 	 * Params:
933 	 *     stretch = true for enabling stretching, false to disable.
934 	 *
935 	 * Returns:
936 	 *     This widget to aid method chaining.
937 	 */
938 	public auto setStretch(this T)(bool stretch)
939 	{
940 		this._stretch = stretch;
941 
942 		if (this._parent)
943 		{
944 			this._tk.eval("%s column %s -stretch %s", this._parent.id, this.id, this._stretch);
945 		}
946 
947 		return cast(T) this;
948 	}
949 
950 	/**
951 	 * Set the width of the column.
952 	 *
953 	 * Params:
954 	 *     width = The width in pixels.
955 	 *
956 	 * Returns:
957 	 *     This widget to aid method chaining.
958 	 */
959 	public auto setWidth(this T)(int width)
960 	{
961 		this._width = width;
962 
963 		if (this._parent)
964 		{
965 			this._tk.eval("%s column %s -width %s", this._parent.id, this.id, this._width);
966 		}
967 
968 		return cast(T) this;
969 	}
970 }
971 
972 /**
973  * A class representing a row in the tree view.
974  */
975 class TreeViewRow
976 {
977 	/**
978 	 * The row id. This is populated by the treeview once the row has been 
979 	 * inserted.
980 	 */
981 	private string _rowId;
982 
983 	/**
984 	 * An array containing the column values.
985 	 */
986 	private string[] _values;
987 
988 	/**
989 	 * Boolean representing if the row was set to be open when created.
990 	 */
991 	private bool _isOpen;
992 
993 	/**
994 	 * An array containing the tags.
995 	 */
996 	private string[] _tags;
997 
998 	/**
999 	 * An array containing the child rows.
1000 	 */
1001 	public TreeViewRow[] children;
1002 
1003 	/**
1004 	 * Constructor.
1005 	 */
1006 	private this()
1007 	{
1008 	}
1009 
1010 	/**
1011 	 * Constructor.
1012 	 *
1013 	 * Params:
1014 	 *     values = The values of the columns.
1015 	 *     isOpen = Whether or not to display the row open.
1016 	 *     tags = The tags to associate to this row.
1017 	 */
1018 	public this(string[] values, bool isOpen = false, string[] tags = [])
1019 	{
1020 		assert(values.length, "There must be at least 1 value in the row.");
1021 
1022 		this._values = values;
1023 		this._isOpen = isOpen;
1024 		this._tags   = tags;
1025 	}
1026 
1027 	/**
1028 	 * Get the row id. This is populated by the treeview once the row has been 
1029 	 * inserted.
1030 	 *
1031 	 * Returns:
1032 	 *     A string containing the row id.
1033 	 */
1034 	public @property string id()
1035 	{
1036 		return this._rowId;
1037 	}
1038 
1039 	/**
1040 	 * Get the data column values.
1041 	 *
1042 	 * Returns:
1043 	 *     An array containing the data values.
1044 	 */
1045 	public @property string[] values()
1046 	{
1047 		return this._values;
1048 	}
1049 
1050 	/**
1051 	 * Get if the row was open.
1052 	 *
1053 	 * Returns:
1054 	 *     true if the row was set to be open, false if not.
1055 	 */
1056 	public @property bool isOpen()
1057 	{
1058 		return this._isOpen;
1059 	}
1060 
1061 	/**
1062 	 * Get the tags.
1063 	 *
1064 	 * Returns:
1065 	 *     An array of tags assocaited to this row.
1066 	 */
1067 	public @property string[] tags()
1068 	{
1069 		return this._tags;
1070 	}
1071 
1072 	/**
1073 	 * String representation.
1074 	 */
1075 	debug override public string toString()
1076 	{
1077 		return format("Row Id: %s, Values: %s, isOpen: %s, Tags: %s, Children: %s", this.id, this.values, this.isOpen, this.tags, this.children);
1078 	}
1079 }
1080 
1081 /**
1082  * Tree view selection modes.
1083  */
1084 enum TreeViewSelectionMode : string
1085 {
1086 	browse   = "browse",   /// The default mode, allows one selection only.
1087 	extended = "extended", /// Allows multiple selections to be made.
1088 	none     = "none",     /// Disabled all selection.
1089 }