1 module PixelPerfectEngine.concrete.elements.listview;
2 
3 import PixelPerfectEngine.concrete.elements.base;
4 import PixelPerfectEngine.concrete.elements.scrollbar;
5 
6 import PixelPerfectEngine.system.etc : clamp, min, max;
7 
8 import PixelPerfectEngine.system.input.types : TextInputFieldType;
9 
10 /**
11  * Defines a single item in the listview.
12  * Draws directly onto the canvas to avoit using multiple framebuffers.
13  * Can be inherited from, and that's when non-alphabetical ordering can be implemented.
14  */
15 public class ListViewItem {
16 	/**
17 	 * Defines a single field (cell) in a listview.
18 	 */
19 	public struct Field {
20 		private uint		flags;		///Stores various flags (constraints, etc.)
21 		public Text			text;		///Stores the text of this field if there's any.
22 		public ABitmap		bitmap;		///Custom bitmap, can be 32 bit if target enables it.
23 		static enum IS_EDITABLE = 1 << 0;///Set if field is text editable.
24 		static enum INTEGER_ONLY = 1 << 1;///Set if field only accepts integer numbers.
25 		static enum NUMERIC_ONLY = 1 << 2;///Set if field only accepts numeric values (integer or floating-point).
26 		static enum FORCE_FP = 1 << 3;	///Forces number to be displayed as floating point.
27 		static enum FORMAT_FLAGS = 0xF0;///Used to set and read formatting flags.
28 		///Used to set numerical formatting styles
29 		///NOTE: Not all formats support decimal types
30 		enum FormatFlags : uint {
31 			Context		= 0x00,			///Detected from input context
32 			HexH 		= 0x10,			///Hexanumeric indicated by letter `h` at the end
33 			HexX		= 0x20,			///Hexanumeric indicated by `0x` at the beginning
34 			OctH 		= 0x30,			///Octonumeric indicated by letter `o` at the end
35 			OctX		= 0x40,			///Octonumeric indicated by `0o` at the beginning
36 			BinH 		= 0x50,			///Binary indicated by letter `b` at the end
37 			BinX		= 0x60,			///Binary indicated by `0b` at the beginning
38 			Dec			= 0x70,			///Decimal formatting
39 		}
40 		/**
41 		 * Default constructor.
42 		 */
43 		this(Text text, ABitmap bitmap, FormatFlags frmtFlags, bool editable = false, bool numOnly = false, bool fp = false) 
44 				@nogc @safe pure nothrow {
45 			this.text = text;
46 			this.bitmap = bitmap;
47 			this.flags = frmtFlags;
48 			if (editable) flags |= IS_EDITABLE;
49 			if (numOnly) {
50 				flags |= NUMERIC_ONLY;
51 				if (fp) {
52 					flags |= FORCE_FP;
53 				} else {
54 					flags |= INTEGER_ONLY;
55 				}
56 			}
57 		}
58 		///Returns whether the field is editable.
59 		public @property bool editable() @nogc @safe pure nothrow const {
60 			return flags & IS_EDITABLE ? true : false;
61 		}
62 		///Sets whether the field is editable. Returns the new value.
63 		public @property bool editable(bool val) @nogc @safe pure nothrow {
64 			if (val) flags |= IS_EDITABLE;
65 			else flags &= ~IS_EDITABLE;
66 			return flags & IS_EDITABLE ? true : false;
67 		}
68 		///Returns whether the field is integer only.
69 		public @property bool integerOnly() @nogc @safe pure nothrow const {
70 			return flags & INTEGER_ONLY ? true : false;
71 		}
72 		///Sets whether the field is integer only. Returns the new value.
73 		public @property bool integerOnly(bool val) @nogc @safe pure nothrow {
74 			if (val) flags |= INTEGER_ONLY;
75 			else flags &= ~INTEGER_ONLY;
76 			return flags & INTEGER_ONLY ? true : false;
77 		}
78 		///Returns whether the field is numeric only.
79 		public @property bool numericOnly() @nogc @safe pure nothrow const {
80 			return flags & NUMERIC_ONLY ? true : false;
81 		}
82 		///Sets whether the field is numeric only. Returns the new value.
83 		public @property bool numericOnly(bool val) @nogc @safe pure nothrow {
84 			if (val) flags |= NUMERIC_ONLY;
85 			else flags &= ~NUMERIC_ONLY;
86 			return flags & NUMERIC_ONLY ? true : false;
87 		}
88 		///Returns whether the field forces floating point.
89 		public @property bool forceFP() @nogc @safe pure nothrow const {
90 			return flags & FORCE_FP ? true : false;
91 		}
92 		///Sets whether the field forces floating point. Returns the new value.
93 		public @property bool forceFP(bool val) @nogc @safe pure nothrow {
94 			if (val) flags |= FORCE_FP;
95 			else flags &= ~FORCE_FP;
96 			return flags & FORCE_FP ? true : false;
97 		}
98 		///Returns the field's format flags.
99 		public @property FormatFlags formatFlags() @nogc @safe pure nothrow const {
100 			return cast(FormatFlags)(flags & FORMAT_FLAGS);
101 		}
102 		///Sets whether the field is editable. Returns the new value.
103 		public @property FormatFlags formatFlags(FormatFlags val) @nogc @safe pure nothrow {
104 			flags |= val;
105 			return cast(FormatFlags)(flags & FORMAT_FLAGS);
106 		}
107 	}
108 	/**
109 	 * Stores the list of items to be displayed.
110 	 */
111 	public Field[]		fields;
112 	///Height of this item.
113 	public int			height;
114 	/**
115 	 * Creates a list view item from texts.
116 	 */
117 	this (int height, Text[] fields) @safe pure nothrow {
118 		this.height = height;
119 		this.fields.reserve = fields.length;
120 		foreach (Text key; fields) {
121 			this.fields ~= Field(key, null, Field.FormatFlags.Context);
122 		}
123 	}
124 	/**
125 	 * Creates a ListViewItem from fields directly.
126 	 */
127 	this (int height, Field[] fields) @nogc @safe pure nothrow {
128 		this.height = height;
129 		this.fields = fields;
130 	}
131 	/**
132 	 * Creates a ListViewItem with default text formatting.
133 	 */
134 	this (int height, dstring[] ds) @safe nothrow {
135 		this.height = height;
136 		fields.reserve = ds.length;
137 		foreach (dstring key ; ds) {
138 			this.fields ~= Field(new Text(key, globalDefaultStyle.getChrFormatting("ListViewHeader")), null, 
139 					Field.FormatFlags.Context);
140 		}
141 	}
142 	/**
143 	 * Creates a ListViewItem with default text formatting and input type.
144 	 */
145 	this (int height, dstring[] ds, TextInputFieldType[] inputTypes) @safe nothrow {
146 		this.height = height;
147 		fields.reserve = ds.length;
148 		assert (ds.length == inputTypes.length, "Mismatch in inputTypes and text length");
149 		for (size_t i ; i < ds.length ; i++) {
150 			Field f = Field(new Text(ds[i], globalDefaultStyle.getChrFormatting("ListViewHeader")), null, 
151 					Field.FormatFlags.Context);
152 			final switch (inputTypes[i]) with (TextInputFieldType) {
153 				case Text:
154 					f.editable = true;
155 					break;
156 				case Numeric:
157 					f.numericOnly = true;
158 					goto case Text;
159 				case Integer:
160 					f.integerOnly = true;
161 					goto case Text;
162 				case Dec:
163 					f.formatFlags = Field.FormatFlags.Dec;
164 					goto case Integer;
165 				case Hex:
166 					f.formatFlags = Field.FormatFlags.HexH;
167 					goto case Integer;
168 				case Oct:
169 					f.formatFlags = Field.FormatFlags.OctH;
170 					goto case Integer;
171 				case Bin:
172 					f.formatFlags = Field.FormatFlags.BinH;
173 					goto case Integer;
174 				case None, ASCIIText:
175 					break;
176 			}
177 			fields ~= f;
178 		}
179 	}
180 	/**
181 	 * Accesses fields like an array.
182 	 */
183 	public ref Field opIndex(size_t index) @nogc @safe pure nothrow {
184 		return fields[index];
185 	}
186 	/**
187 	 * Accesses fields like an array.
188 	 */
189 	public Field opIndexAssign(Field value, size_t index) @nogc @safe pure nothrow {
190 		fields[index] = value;
191 		return value;
192 	}
193 	///Returns the amount of fields in this item.
194 	public size_t length() @nogc @safe pure nothrow {
195 		return fields.length;
196 	}
197 	/**
198 	 * Draws the ListViewItem. Draw parameters are supplied via a nested class found in ListView.
199 	 */
200 	public void draw(ListView parent) {
201 		StyleSheet ss = parent.drawParams.ss;
202 		Box target = parent.drawParams.target;
203 		Box t = Box(target.left, target.top, target.left, target.bottom);
204 		Point offset = Point(parent.drawParams.offsetP, parent.drawParams.offsetFR);
205 		for (int i = parent.drawParams.offsetC ; i <= parent.drawParams.targetC ; i++) {
206 			t.right = min(t.left + parent.drawParams.columnWidths[i] - offset.x, target.right);
207 			parent.drawTextSL(t.pad(ss.drawParameters["ListViewColPadding"], ss.drawParameters["ListViewRowPadding"]), 
208 					fields[i].text, offset);
209 			t.left = t.right;
210 			offset.x = 0;
211 		}
212 		parent.drawParams.target.relMove(0, height - parent.drawParams.offsetFR);
213 		parent.drawParams.offsetFR = 0;
214 	}
215 }
216 /**
217  * Defines the header of a ListView.
218  * Extended from a ListViewItem.
219  */
220 public class ListViewHeader : ListViewItem {
221 	public int[]				columnWidths;	///Width of each columns
222 	///Default CTOR
223 	this(int height, int[] columnWidths, Text[] fields) @safe pure nothrow {
224 		assert (columnWidths.length == fields.length, "Lenght mismatch between the two arrays!");
225 		this.columnWidths = columnWidths;
226 		super(height, fields);
227 	}
228 	///CTOR for creating fields with default text formatting
229 	this(int height, int[] columnWidths, dstring[] ds) @safe nothrow {
230 		Text[] fields;
231 		fields.reserve = ds.length;
232 		foreach (dstring key; ds) {
233 			fields ~= new Text(key, globalDefaultStyle.getChrFormatting("ListViewHeader"));
234 		}
235 		this(height, columnWidths, fields);
236 	}
237 	/**
238 	 * Draws the header. Draw parameters are supplied via a nested class found in ListView.
239 	 */
240 	public override void draw(ListView parent) {
241 		if (!height) return;
242 		StyleSheet ss = parent.drawParams.ss;
243 		Box target = parent.drawParams.target;
244 		Box t = Box(target.left, target.top, target.left, target.bottom);
245 		Point offset = Point(parent.drawParams.offsetP, 0);
246 		for (int i = parent.drawParams.offsetC ; i <= parent.drawParams.targetC ; i++) {
247 			t.right = min(t.left + parent.drawParams.columnWidths[i] - offset.x, target.right);
248 			if (!offset.x) {
249 				parent.drawLine(t.cornerUL, t.cornerLL, ss.getColor("windowascent"));
250 			}
251 			if (t.left + parent.drawParams.columnWidths[i] < target.right) {
252 				Point from = t.cornerUR, to = t.cornerLR;
253 				from.x = from.x - 1;
254 				to.x = to.x - 1;
255 				parent.drawLine(from, to, ss.getColor("windowdescent"));
256 			}
257 			with (parent) {
258 				drawLine(t.cornerUL, t.cornerUR, ss.getColor("windowascent"));
259 				drawLine(t.cornerLL, t.cornerLR, ss.getColor("windowdescent"));
260 				drawTextSL(t.pad(ss.drawParameters["ListViewColPadding"], ss.drawParameters["ListViewRowPadding"]), fields[i].text, 
261 						offset);
262 			}
263 			t.left = t.right;
264 			offset.x = 0;
265 		}
266 		parent.drawParams.target.relMove(0, height);
267 	}
268 }
269 /**
270  * Implements a basic ListView 
271  */
272 public class ListView : WindowElement, ElementContainer, TextInputListener {
273 	///Supplies draw parameters to the items
274 	public class DrawParameters {
275 		///StyleSheet that is being used currently
276 		StyleSheet 				ss;
277 		///Contains the reference to the header's columnWidth attribute
278 		int[]					columnWidths;
279 		///The first column to be drawn
280 		const int				offsetC;
281 		///The last column to be drawn
282 		const int				targetC;
283 		///Offset in pixels for the first column
284 		const int				offsetP;
285 		///Offset of the first row. Should be set to zero after the first row has been drawn.
286 		int						offsetFR;
287 		///The prelimiter where the item should be drawn.
288 		Box						target;
289 		///CTOR
290 		this (StyleSheet ss, int[] columnWidths, const int offsetC, const int targetC, const int offsetP, 
291 				int offsetFR) @safe @nogc pure nothrow {
292 			this.ss = ss;
293 			this.columnWidths = columnWidths;
294 			this.offsetC = offsetC;
295 			this.targetC = targetC;
296 			this.offsetP = offsetP;
297 			this.offsetFR = offsetFR;
298 		}
299 	}
300 	protected HorizScrollBar	horizSlider;	///Horizontal scroll bar.
301 	protected VertScrollBar		vertSlider;		///Vertical scroll bar.
302 	///The header of the ListView. 
303 	///Accessed in a safe manner to ensure it's being updated on the output raster.
304 	protected ListViewHeader	_header;
305 	///Entries in the ListView.
306 	///Accessed in a safe manner to ensure it's being updated on the output raster and that the number of columns match.
307 	protected ListViewItem[]	entries;
308 	protected int				selection;		///Selected item's number, or -1 if none selected.
309 	protected int				hSelection;		///Horizontal selection for text editing.
310 	protected int				tselect;		///Lenght of selected characters.
311 	protected int				cursorPos;		///Position of cursor.
312 	protected int				horizTextOffset;///Horizontal text offset if text cannot fit the cell.
313 	///Text editing area.
314 	protected Box				textArea;
315 	//protected Text				oldText;
316 	///Holds shared draw parameters that are used when the element is being drawn.
317 	///Should be set to null otherwise.
318 	public DrawParameters		drawParams;
319 	///Called when an item is selected
320 	public EventDeleg			onItemSelect;
321 	///Called when text input is finished and accepted
322 	public EventDeleg			onTextInput;
323 	protected static enum	EDIT_EN = 1<<9;
324 	protected static enum	MULTICELL_EDIT_EN = 1<<10;
325 	protected static enum	TEXTINPUT_EN = 1<<11;
326 	protected static enum	INSERT = 1<<12;
327 	///Standard CTOR
328 	public this(ListViewHeader header, ListViewItem[] entries, string source, Box position) {
329 		_header = header;
330 		this.entries = entries;
331 		this.source = source;
332 		this.position = position;
333 		recalculateTotalSizes();
334 	}
335 	/**
336 	 * Accesses data entries in a safe manner.
337 	 */
338 	public ListViewItem opIndex(size_t index) @nogc @safe pure nothrow {
339 		return entries[index];
340 		/+scope(exit) {
341 			assert(entries[index].length == _header.length, "Column number mismatch error!");
342 		}+/
343 	}
344 	/**
345 	 * Accesses data entries in a safe manner.
346 	 */
347 	public ListViewItem opIndexAssign(ListViewItem value, size_t index) @safe pure {
348 		if (value.length == _header.length) {
349 			if (entries.length == index) {
350 				entries ~= value;
351 			} else {
352 				entries[index] = value;
353 			}
354 		} else throw new Exception("Column number mismatch!");
355 		return value;
356 	}
357 	/**
358 	 * Allows to append a single element to the entry list.
359 	 */
360 	public ListViewItem opOpAssign(string op)(ListViewItem value) {
361 		static if (op == "~" || op == "+") {
362 			if (value.length == _header.length) {
363 				entries ~= value;
364 			} else throw new Exception("Column number mismatch!");
365 		} else static assert (0, "Unsupported operator!");
366 		return value;
367 	}
368 	/**
369 	 * Allows to append multiple elements to the entry list.
370 	 */
371 	public ListViewItem[] opOpAssign(string op)(ListViewItem[] value) {
372 		static if (op == "~" || op == "+") {
373 			foreach (ListViewItem key; value) {
374 				if (key.length == _header.length) {
375 					entries ~= key;
376 				} else throw new Exception("Column number mismatch!");
377 			}
378 		} else static assert (0, "Unsupported operator!");
379 		return value;
380 	}
381 	override public void draw() {
382 		StyleSheet ss = getStyleSheet;
383 		if (flags & TEXTINPUT_EN) { //only redraw the editing cell in this case
384 			const int textPadding = ss.drawParameters["TextSpacingSides"];
385 			
386 			clearArea(textArea);
387 			//drawBox(position, ss.getColor("windowascent"));
388 			
389 			//draw cursor
390 			//if (flags & ENABLE_TEXT_EDIT) {
391 			//calculate cursor first
392 			Box cursor = Box(textArea.left + textPadding, textArea.top + textPadding, textArea.left + textPadding, textArea.bottom - textPadding);
393 			cursor.left += text.getWidth(0, cursorPos) - horizTextOffset;
394 			//cursor must be at least single pixel wide
395 			cursor.right = cursor.left;
396 			if (tselect) {
397 				cursor.right += text.getWidth(cursorPos, cursorPos + tselect);
398 			} else if (flags & INSERT) {
399 				if (cursorPos < text.charLength) cursor.right += text.getWidth(cursorPos, cursorPos+1);
400 				else cursor.right += text.font.chars(' ').xadvance;
401 			} else {
402 				cursor.right++;
403 			}
404 			//Clamp down if cursor is wider than the text editing area
405 			cursor.right = cursor.right <= textArea.right - textPadding ? cursor.right : textArea.right - textPadding;
406 			//Draw cursor
407 			parent.drawFilledBox(cursor, ss.getColor("selection"));
408 
409 			//}
410 			//draw text
411 			parent.drawTextSL(textArea - textPadding, text, Point(horizTextOffset, 0));
412 		} else {
413 			parent.clearArea(position);
414 
415 			parent.drawBox(position, ss.getColor("windowascent"));
416 			Point upper = Point(0, position.top + _header.height);
417 			Point lower = Point(0, position.bottom);
418 			{	///Calculate first column stuff
419 				int offsetP, offsetC, targetC, targetP;
420 				if (horizSlider) { 
421 					offsetP = horizSlider.value();
422 					//int offsetC;
423 					for (; _header.columnWidths[offsetC] < offsetP ; offsetC++) {
424 						offsetP -= _header.columnWidths[offsetC];
425 					}
426 					///Calculate last column number
427 					//int targetC;
428 					targetP = horizSlider.value() + position.width;
429 					for (; _header.columnWidths[targetC] < targetP ; targetC++) {
430 						targetP -= _header.columnWidths[targetC];
431 					}
432 					//targetP = _header.columnWidths[targetC] - targetP;
433 					lower.y -= horizSlider.getPosition().height;
434 				} else {
435 					targetC = cast(int)_header.columnWidths.length - 1;
436 				}
437 				drawParams = new DrawParameters(ss, _header.columnWidths, offsetC, targetC, offsetP, 0);
438 			}
439 
440 			drawParams.target = Box(position.left, position.top, position.right, position.top + _header.height);
441 
442 			if (vertSlider) {
443 				drawParams.target.right -= vertSlider.getPosition.width;
444 
445 			}
446 
447 			_header.draw(this);
448 			/+Point upper = Point(drawParams.columnWidths[drawParams.offsetC] + position.left, position.top + _header.height);
449 			Point lower = Point(upper.x, position.bottom);+/
450 			int firstRow, lastRow;
451 			if (vertSlider) {
452 				int pixelsTotal = vertSlider.value();
453 				for (; entries[firstRow].height < pixelsTotal ; firstRow++) {
454 					pixelsTotal -= entries[firstRow].height;
455 				}
456 				drawParams.offsetFR = pixelsTotal;
457 				pixelsTotal += position.height;
458 				pixelsTotal -= _header.height;
459 				if (horizSlider) pixelsTotal -= horizSlider.getPosition().height;
460 				lastRow = firstRow;
461 				for (; entries[lastRow].height < pixelsTotal ; lastRow++) {
462 					pixelsTotal -= entries[lastRow].height;
463 				}
464 			} else {
465 				lastRow = cast(int)entries.length - 1;
466 			}
467 
468 			for (int i = firstRow ; i <= lastRow ; i++) {
469 				if (ss.getColor("ListViewHSep") && i != lastRow) {
470 					parent.drawLine(drawParams.target.cornerLL, drawParams.target.cornerLR, ss.getColor("ListViewHSep"));
471 				}
472 				if (selection == i) {
473 					Box target = drawParams.target - 1;
474 					target.bottom -= drawParams.offsetFR;
475 					parent.drawFilledBox(target, ss.getColor("selection"));
476 				}
477 				entries[i].draw(this);	
478 			}
479 
480 			if (ss.getColor("ListViewVSep")) {
481 				for (int i = drawParams.offsetC ; i <= drawParams.targetC ; i++) {
482 					upper.x = drawParams.columnWidths[i];
483 					lower.x = drawParams.columnWidths[i];
484 					parent.drawLine(upper, lower, ss.getColor("ListViewVSep"));
485 				}
486 			}
487 			if (horizSlider) horizSlider.draw;
488 			if (vertSlider) vertSlider.draw;
489 
490 			drawParams = null;
491 		}
492 	}
493 	/**
494 	 * Returns the number of the selected item.
495 	 */
496 	public @property int value() @nogc @safe pure nothrow const {
497 		return selection;
498 	}
499 	/**
500 	 * Sets the selected item and then does a redraw.
501 	 */
502 	public @property int value(int val) {
503 		selection = val;
504 		draw;
505 		return selection;
506 	}
507 	/**
508 	 * Enables or disables the text editing of this element.
509 	 */
510 	public @property bool editEnable(bool val) @nogc @safe pure nothrow {
511 		if (val) flags |= EDIT_EN;
512 		else flags &= ~EDIT_EN;
513 		return flags & EDIT_EN ? true : false;
514 	}
515 	/**
516 	 * Returns true if text editing is enabled.
517 	 */
518 	public @property bool editEnable() @nogc @safe pure nothrow const {
519 		return flags & EDIT_EN ? true : false;
520 	}
521 	/**
522 	 * Enables or disables editing for multiple cells.
523 	 * If disabled, the first cell with editing enabled will be able to be edited.
524 	 */
525 	public @property bool multicellEditEnable(bool val) @nogc @safe pure nothrow {
526 		if (val) flags |= MULTICELL_EDIT_EN;
527 		else flags &= ~MULTICELL_EDIT_EN;
528 		return flags & MULTICELL_EDIT_EN ? true : false;
529 	}
530 	/**
531 	 * Returns true if text editing for multiple cells is enabled.
532 	 */
533 	public @property bool multicellEditEnable() @nogc @safe pure nothrow const {
534 		return flags & MULTICELL_EDIT_EN ? true : false;
535 	}
536 	/**
537 	 * Sets a new header, also able to supply new entries.
538 	 */
539 	public void setHeader(ListViewHeader header, ListViewItem[] entries) {
540 		_header = header;
541 		foreach (ListViewItem key; entries) {
542 			assert(key.length == header.length);
543 		}
544 		this.entries = entries;
545 		refresh();
546 		draw();
547 	}
548 	/**
549 	 * Removes an item from the entries.
550 	 * Returns the removed entry.
551 	 */
552 	public ListViewItem removeEntry(size_t index) {
553 		import std.algorithm.mutation : remove;
554 		ListViewItem result = entries[index];
555 		entries = entries.remove(index);
556 		if (selection >= entries.length) selection--;
557 		//draw;
558 		return result;
559 	}
560 	/**
561 	 * Removes all entries in the list.
562 	 */
563 	public void clear() @safe {
564 		entries.length = 0;
565 	}
566 	/**
567 	 * Refreshes the list view.
568 	 * Must be called every time when adding new items is finished.
569 	 */
570 	public void refresh() {
571 		selection = -1;
572 		recalculateTotalSizes;
573 		draw;
574 	}
575 	/**
576 	 * Recalculates the total width and height of the list view's field, also generates scrollbars if needed.
577 	 */
578 	protected void recalculateTotalSizes() {
579 		int totalWidth, totalHeight;
580 		foreach (i ; _header.columnWidths) {
581 			totalWidth += i;
582 		}
583 		foreach (ListViewItem key; entries) {
584 			totalHeight += key.height;
585 		}
586 		totalHeight += _header.height;
587 		StyleSheet ss = getStyleSheet();
588 		bool needsVSB, needsHSB;
589 		if (totalWidth > position.width) 
590 			needsHSB = true;
591 		if (totalHeight > position.height)
592 			needsVSB = true;
593 		if (needsVSB && totalWidth > position.width - ss.drawParameters["VertScrollBarSize"])
594 			needsHSB = true;
595 		if (needsHSB && totalHeight > position.height - ss.drawParameters["HorizScrollBarSize"])
596 			needsVSB = true;
597 		totalHeight -= _header.height;
598 		if (needsVSB) {
599 			const int maxvalue = needsHSB ? totalHeight - position.height - ss.drawParameters["HorizScrollBarSize"] : 
600 					totalHeight - position.height;
601 			
602 			const Box target = Box(position.right - ss.drawParameters["HorizScrollBarSize"] + 2, position.top, 
603 					position.right, needsVSB ? position.bottom - ss.drawParameters["VertScrollBarSize"] : position.bottom);
604 			vertSlider = new VertScrollBar(maxvalue, source ~ "VSB", target);
605 			vertSlider.setParent(this);
606 			vertSlider.onScrolling = &scrollBarEventOut;
607 		} else vertSlider = null;
608 		if (needsHSB){
609 			const int maxvalue = needsVSB ? totalWidth - position.width - ss.drawParameters["VertScrollBarSize"] : 
610 					totalWidth - position.width;
611 			const Box target = Box(position.left, position.bottom - ss.drawParameters["VertScrollBarSize"] + 2, 
612 					needsVSB ? position.right - ss.drawParameters["HorizScrollBarSize"] : position.right,
613 					position.bottom);
614 			horizSlider = new HorizScrollBar(maxvalue, source ~ "VSB", target);
615 			horizSlider.setParent(this);
616 			horizSlider.onScrolling = &scrollBarEventOut;
617 		} else horizSlider = null;
618 	}
619 	protected void scrollBarEventOut(Event ev) {
620 		draw;
621 	}
622 	/**
623 	 * Returns the absolute position of the element.
624 	 */
625 	public Box getAbsolutePosition(WindowElement sender) {
626 		return parent.getAbsolutePosition(sender);
627 	}
628 	/**
629 	 * Gives focus to the element if applicable
630 	 */
631 	public void requestFocus(WindowElement sender) {
632 		
633 	}
634 	/**
635 	 * Sets the cursor to the given type on request.
636 	 */
637 	public void requestCursor(CursorType type) {
638 
639 	}
640 	///Draws a line.
641 	public void drawLine(Point from, Point to, ubyte color) @trusted {
642 		parent.drawLine(from, to, color);
643 	}
644 	///Draws a line pattern.
645 	public void drawLinePattern(Point from, Point to, ubyte[] pattern) @trusted {
646 		parent.drawLinePattern(from, to, pattern);
647 	}
648 	///Draws an empty rectangle.
649 	public void drawBox(Box target, ubyte color) @trusted {
650 		parent.drawBox(target, color);
651 	}
652 	///Draws an empty rectangle with line patterns.
653 	public void drawBoxPattern(Box target, ubyte[] pattern) @trusted {
654 		parent.drawBoxPattern(target, pattern);
655 	}
656 	///Draws a filled rectangle with a specified color.
657 	public void drawFilledBox(Box target, ubyte color) @trusted {
658 		parent.drawFilledBox(target, color);
659 	}
660 	///Pastes a bitmap to the given point using blitter, which threats color #0 as transparency.
661 	public void bitBLT(Point target, ABitmap source) @trusted {
662 		parent.bitBLT(target, source);
663 	}
664 	///Pastes a slice of a bitmap to the given point using blitter, which threats color #0 as transparency.
665 	public void bitBLT(Point target, ABitmap source, Box slice) @trusted {
666 		parent.bitBLT(target, source, slice);
667 	}
668 	///Pastes a repeated bitmap pattern over the specified area.
669 	public void bitBLTPattern(Box target, ABitmap pattern) @trusted {
670 		parent.bitBLTPattern(target, pattern);
671 	}
672 	///XOR blits a repeated bitmap pattern over the specified area.
673 	public void xorBitBLT(Box target, ABitmap pattern) @trusted {
674 		parent.xorBitBLT(target, pattern);
675 	}
676 	///XOR blits a color index over a specified area.
677 	public void xorBitBLT(Box target, ubyte color) @trusted {
678 		parent.xorBitBLT(target, color);
679 	}
680 	///Fills an area with the specified color.
681 	public void fill(Point target, ubyte color, ubyte background = 0) @trusted {
682 		parent.fill(target, color, background);
683 	}
684 	///Draws a single line text within the given prelimiter.
685 	public void drawTextSL(Box target, Text text, Point offset) @trusted {
686 		parent.drawTextSL(target, text, offset);
687 	}
688 	///Draws a multi line text within the given prelimiter.
689 	public void drawTextML(Box target, Text text, Point offset) @trusted {
690 		parent.drawTextML(target, text, offset);
691 	}
692 	///Clears the area within the target
693 	public void clearArea(Box target) @trusted {
694 		parent.clearArea(target);
695 	}
696 	///Passes mouse click event
697 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
698 		///TODO: Handle mouse click when in text editing mode
699 
700 		if (vertSlider) {
701 			const Box p = vertSlider.getPosition();
702 			if (p.isBetween(mce.x, mce.y)) {
703 				mce.x -= p.left - position.left;
704 				mce.y -= p.top - position.top;
705 				vertSlider.passMCE(mec, mce);
706 				return;
707 			}
708 		}
709 		if (horizSlider) {
710 			const Box p = horizSlider.getPosition();
711 			if (p.isBetween(mce.x, mce.y)) {
712 				mce.x -= p.left - position.left;
713 				mce.y -= p.top - position.top;
714 				horizSlider.passMCE(mec, mce);
715 				return;
716 			}
717 		}
718 
719 		//if (mce.button != MouseButton.Left && !mce.state) return;
720 
721 		mce.x -= position.left;
722 		mce.y -= position.top;
723 		if (entries.length && mce.y > _header.height && mce.button == MouseButton.Left && mce.state) {
724 			textArea.top = position.top;
725 			textArea.left = position.left;
726 			mce.y -= _header.height;
727 			int pixelsTotal = mce.y, pos;
728 			if (vertSlider) ///calculate outscrolled area
729 				pixelsTotal += vertSlider.value;
730 			while (pos < entries.length) {
731 				if (pixelsTotal > entries[pos].height) {
732 					pixelsTotal -= entries[pos].height;
733 					textArea.top += entries[pos].height;
734 					if (pos + 1 < entries.length)
735 						pos++;
736 				} else {
737 					break;
738 				}
739 			}
740 			if (pos >= entries.length) {
741 				selection = -1;
742 			} else if (selection == pos && (flags & EDIT_EN)) {
743 				//Calculate horizontal selection for Multicell editing if needed
744 				if (flags & MULTICELL_EDIT_EN) {
745 
746 				} else {
747 					foreach (size_t i, ListViewItem.Field f ; entries[selection].fields) {
748 						if (f.editable) {
749 							hSelection = cast(int)i;
750 							if (vertSlider) textArea.top -= vertSlider.value;
751 							if (horizSlider) textArea.left -= horizSlider.value;
752 							textArea.top += _header.height;
753 							with (textArea) {
754 								bottom = entries[selection].height + textArea.top;
755 								right = _header.columnWidths[i] + textArea.left;
756 								left = max(textArea.left, position.left);
757 								top = max(textArea.top, position.top);
758 								right = min(textArea.right, position.right);
759 								bottom = min(textArea.bottom, position.bottom);
760 							}
761 							text = entries[selection][hSelection].text;
762 							cursorPos = 0;
763 							tselect = cast(int)text.charLength;
764 							//oldText = text;
765 							inputHandler.startTextInput(this);
766 							break;
767 						}
768 						textArea.left += _header.columnWidths[i];
769 					}
770 				}
771 				selection = pos;
772 			} else 
773 				selection = pos;
774 
775 			if (onItemSelect !is null && selection != -1)
776 				onItemSelect(new Event(this, entries[selection], EventType.Selection, SourceType.WindowElement));
777 		} else if (!entries.length)
778 			selection = -1;
779 		
780 		draw();
781 	}
782 	///Passes mouse move event
783 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
784 		if (vertSlider) {
785 			const Box p = vertSlider.getPosition();
786 			if (p.isBetween(mme.x, mme.y)) {
787 				mme.x -= p.left - position.left;
788 				mme.y -= p.top - position.top;
789 				vertSlider.passMME(mec, mme);
790 				return;
791 			}
792 		}
793 		if (horizSlider) {
794 			const Box p = horizSlider.getPosition();
795 			if (p.isBetween(mme.x, mme.y)) {
796 				mme.x -= p.left - position.left;
797 				mme.y -= p.top - position.top;
798 				horizSlider.passMME(mec, mme);
799 				return;
800 			}
801 		}
802 	}
803 	///Passes mouse scroll event
804 	public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
805 		if (horizSlider) horizSlider.passMWE(mec, mwe);
806 		if (vertSlider) vertSlider.passMWE(mec, mwe);
807 	}
808 	/**
809 	 * Puts a PopUpElement on the GUI.
810 	 */
811 	public void addPopUpElement(PopUpElement p) {
812 		parent.addPopUpElement(p);
813 	}
814 	/**
815 	 * Puts a PopUpElement on the GUI at the given position.
816 	 */
817 	public void addPopUpElement(PopUpElement p, int x, int y) {
818 		parent.addPopUpElement(p, x, y);
819 	}
820 	/** 
821 	 * Ends the popup session and closes all popups.
822 	 */
823 	public void endPopUpSession(PopUpElement p) {
824 		parent.endPopUpSession(p);
825 	}
826 	/**
827 	 * Closes a single popup element.
828 	 */
829 	public void closePopUp(PopUpElement p) {
830 		parent.closePopUp(p);
831 	}
832 	//Interface `TextInputListener` starts here
833 	/**
834 	 * Passes the inputted text to the target, alongside with a window ID and a timestamp.
835 	 */
836 	public void textInputEvent(uint timestamp, uint windowID, dstring text) {
837 		if (tselect) {
838 			this.text.removeChar(cursorPos, tselect);
839 			tselect = 0;
840 			for(int j ; j < text.length ; j++){
841 				this.text.insertChar(cursorPos++, text[j]);
842 			}
843 		} else if (flags & INSERT) {
844 			for(int j ; j < text.length ; j++){
845 				this.text.overwriteChar(cursorPos++, text[j]);
846 			}
847 		} else {
848 			for(int j ; j < text.length ; j++){
849 				this.text.insertChar(cursorPos++, text[j]);
850 			}
851 		}
852 		const int textPadding = getStyleSheet.drawParameters["TextSpacingSides"];
853 		const Coordinate textPos = Coordinate(textPadding,(position.height / 2) - (this.text.font.size / 2) ,
854 				position.width,position.height - textPadding);
855 		const int x = this.text.getWidth(), cursorPixelPos = this.text.getWidth(0, cursorPos);
856 		if(x > textPos.width) {
857 			 if(cursorPos == this.text.text.length) {
858 				horizTextOffset = x - textPos.width;
859 			 } else if(cursorPixelPos < horizTextOffset) { //Test for whether the cursor would fall out from the current text area
860 				horizTextOffset = cursorPixelPos;
861 			 } else if(cursorPixelPos > horizTextOffset + textPos.width) {
862 				horizTextOffset = horizTextOffset + textPos.width;
863 			 }
864 		}
865 		draw();
866 	}
867 	/**
868 	 * Passes text editing events to the target, alongside with a window ID and a timestamp.
869 	 */
870 	public void textEditingEvent(uint timestamp, uint windowID, dstring text, int start, int length) {
871 		for (int i ; i < length ; i++) {
872 			this.text.overwriteChar(start + i, text[i]);
873 		}
874 		cursorPos = start + length;
875 	}
876 	/**
877 	 * Passes text input key events to the target, e.g. cursor keys.
878 	 */
879 	public void textInputKeyEvent(uint timestamp, uint windowID, TextInputKey key, ushort modifier) {
880 		switch(key) {
881 			case TextInputKey.Enter:
882 				entries[selection][hSelection].text = text;
883 				inputHandler.stopTextInput();
884 				if(onTextInput !is null)
885 					onTextInput(new CellEditEvent(this, entries[selection], selection, hSelection));
886 					//onTextInput(new Event(source, null, null, null, text, 0, EventType.T, null, this));
887 				break;
888 			case TextInputKey.Escape:
889 				//text = oldText;
890 				inputHandler.stopTextInput();
891 				
892 
893 				break;
894 			case TextInputKey.Backspace:
895 				if(cursorPos > 0){
896 					deleteCharacter(cursorPos - 1);
897 					cursorPos--;
898 					draw();
899 				}
900 				break;
901 			case TextInputKey.Delete:
902 				if (tselect) {
903 					
904 				} else deleteCharacter(cursorPos);
905 				draw();
906 				break;
907 			case TextInputKey.CursorLeft:
908 				if (modifier != KeyModifier.Shift) {
909 					tselect = 0;
910 					if(cursorPos > 0){
911 						--cursorPos;
912 						draw();
913 					}
914 				}
915 				break;
916 			case TextInputKey.CursorRight:
917 				if (modifier != KeyModifier.Shift) {
918 					tselect = 0;
919 					if(cursorPos < text.charLength){
920 						++cursorPos;
921 						draw();
922 					}
923 				}
924 				break;
925 			case TextInputKey.Home:
926 				if (modifier != KeyModifier.Shift) {
927 					tselect = 0;
928 					cursorPos = 0;
929 					draw();
930 				}
931 				break;
932 			case TextInputKey.End:
933 				if (modifier != KeyModifier.Shift) {
934 					tselect = 0;
935 					cursorPos = cast(int)text.charLength;
936 					draw();
937 				}
938 				break;
939 			case TextInputKey.Insert:
940 				flags ^= INSERT;
941 				draw();
942 				break;
943 			default:
944 				break;
945 		}
946 	}
947 	/**
948 	 * When called, the listener should drop all text input.
949 	 */
950 	public void dropTextInput() {
951 		flags &= ~TEXTINPUT_EN;
952 		draw;
953 	}
954 	/**
955 	 * Called if text input should be initialized.
956 	 */
957 	public void initTextInput() {
958 		flags |= TEXTINPUT_EN;
959 	}
960 	private void deleteCharacter(size_t n){
961 		text.removeChar(n);
962 	}
963 }