1 /*
2  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Engine, concrete.window module
5  */
6 
7 module PixelPerfectEngine.concrete.window;
8 
9 import PixelPerfectEngine.graphics.bitmap;
10 import PixelPerfectEngine.graphics.draw;
11 import PixelPerfectEngine.graphics.layers;
12 
13 public import PixelPerfectEngine.concrete.elements;
14 public import PixelPerfectEngine.concrete.stylesheet;
15 import PixelPerfectEngine.system.etc;
16 import PixelPerfectEngine.system.inputHandler;
17 
18 import std.algorithm.mutation;
19 import std.stdio;
20 import std.conv;
21 import std.file;
22 import std.path;
23 import std.datetime;
24 
25 /**
26  * Basic window. All other windows are inherited from this class.
27  */
28 public class Window : ElementContainer{
29 	protected WindowElement[] elements, mouseC, keyboardC, scrollC;
30 	protected dstring title;
31 	protected WindowElement draggedElement;
32 	public IWindowHandler parent;
33 	//public Bitmap16Bit[int] altStyleBrush;
34 	protected BitmapDrawer output;
35 	public int header;//, sizeX, sizeY;
36 	protected int moveX, moveY;
37 	protected bool fullUpdate, isActive;
38 	protected string[] extraButtons;
39 	public Coordinate position;
40 	public StyleSheet customStyle;
41 	public static StyleSheet defaultStyle;
42 	public static void delegate() onDrawUpdate;
43 	/**
44 	 * If the current window doesn't contain a custom StyleSheet, it gets from it's parent.
45 	 */
46 	public StyleSheet getStyleSheet(){
47 		if(customStyle is null){
48 			if(parent is null){
49 				return defaultStyle;
50 			}
51 			return parent.getStyleSheet();
52 		}
53 		return customStyle;
54 	}
55 	/**
56 	 * Updates the output of the elements.
57 	 */
58 	public void drawUpdate(WindowElement sender){
59 		/*if(!fullUpdate){
60 			this.draw();
61 		}*/
62 		output.insertBitmap(sender.getPosition().left,sender.getPosition().top,sender.output.output);
63 	}
64 
65 
66 	/**
67 	 * Standard constructor. "size" sets both the initial position and the size of the window.
68 	 * Extra buttons are handled from the StyleSheet, currently unimplemented.
69 	 */
70 	public this(Coordinate size, dstring title, string[] extraButtons = []){
71 		position = size;
72 		output = new BitmapDrawer(position.width(), position.height());
73 		this.title = title;
74 		//sizeX = position.width();
75 		//sizeY = position.height();
76 		//style = 0;
77 		//closeButton = 2;
78 		this.extraButtons = extraButtons;
79 	}
80 	/**
81 	 * Adds a new WindowElement with the given event properties.
82 	 */
83 	public void addElement(WindowElement we, int eventProperties){
84 		elements ~= we;
85 		we.elementContainer = this;
86 		if((eventProperties & EventProperties.KEYBOARD) == EventProperties.KEYBOARD){
87 			keyboardC ~= we;
88 		}
89 		if((eventProperties & EventProperties.MOUSE) == EventProperties.MOUSE){
90 			mouseC ~= we;
91 		}
92 		if((eventProperties & EventProperties.SCROLL) == EventProperties.SCROLL){
93 			scrollC ~= we;
94 		}
95 		we.draw();
96 	}
97 	/**
98 	 * Removes the WindowElement if 'we' is found within its ranges, does nothing otherwise.
99 	 */
100 	public void removeElement(WindowElement we){
101 		int i;
102 		while(elements.length > i){
103 			if(elements[i] == we){
104 				elements = remove(elements, i);
105 				break;
106 			}else{
107 				i++;
108 			}
109 		}
110 		i = 0;
111 		while(mouseC.length > i){
112 			if(mouseC[i] == we){
113 				mouseC = remove(mouseC, i);
114 				break;
115 			}else{
116 				i++;
117 			}
118 		}
119 		i = 0;
120 		while(scrollC.length > i){
121 			if(scrollC[i] == we){
122 				scrollC = remove(scrollC, i);
123 				break;
124 			}else{
125 				i++;
126 			}
127 		}
128 		i = 0;
129 		while(keyboardC.length > i){
130 			if(keyboardC[i] == we){
131 				keyboardC = remove(keyboardC, i);
132 				break;
133 			}else{
134 				i++;
135 			}
136 		}
137 		draw();
138 	}
139 	/**
140 	 * Draws the window. Intended to be used by the WindowHandler.
141 	 */
142 	public void draw(bool drawHeaderOnly = false){
143 		if(output.output.width != position.width || output.output.height != position.height){
144 			output = new BitmapDrawer(position.width(), position.height());
145 
146 		}
147 		//drawing the header
148 		drawHeader();
149 		if(drawHeaderOnly)
150 			return;
151 		output.drawFilledRectangle(0, position.width() - 1, getStyleSheet().getImage("closeButtonA").height,
152 				position.height() - 1, getStyleSheet().getColor("window"));
153 		int y1 = getStyleSheet().getImage("closeButtonA").height;
154 		/*output.drawRectangle(x1, sizeX - 1, 0, y1, getStyleBrush(header));
155 		output.drawFilledRectangle(x1 + (x1/2), sizeX - 1 - (x1/2), y1/2, y1 - (y1/2), getStyleBrush(header).readPixel(x1/2, y1/2));*/
156 
157 		//int headerLength = cast(int)(extraButtons.length == 0 ? position.width - 1 : position.width() - 1 - ((extraButtons.length>>2) * x1) );
158 
159 		//drawing the border of the window
160 		output.drawLine(0, position.width() - 1, y1, y1, getStyleSheet().getColor("windowascent"));
161 		output.drawLine(0, 0, y1, position.height() - 1, getStyleSheet().getColor("windowascent"));
162 		output.drawLine(0, position.width() - 1, position.height() - 1, position.height() - 1,
163 				getStyleSheet().getColor("windowdescent"));
164 		output.drawLine(position.width() - 1, position.width() - 1, y1, position.height() - 1,
165 				getStyleSheet().getColor("windowdescent"));
166 
167 		//output.drawText(x1+1, 1, title, getFontSet(0), 1);
168 
169 		fullUpdate = true;
170 		foreach(WindowElement we; elements){
171 			we.draw();
172 			//output.insertBitmap(we.getPosition().xa,we.getPosition().ya,we.output.output);
173 		}
174 		fullUpdate = false;
175 		parent.drawUpdate(this);
176 		if(onDrawUpdate !is null){
177 			onDrawUpdate();
178 		}
179 	}
180 	/**
181 	 * Draws the header.
182 	 */
183 	protected void drawHeader(){
184 		const ushort colorC = isActive ? getStyleSheet().getColor("WHAtop") : getStyleSheet().getColor("window");
185 		output.drawFilledRectangle(0, position.width() - 1, 0, getStyleSheet().getImage("closeButtonA").height - 1, colorC);
186 		output.insertBitmap(0,0,getStyleSheet().getImage("closeButtonA"));
187 		const int x1 = getStyleSheet().getImage("closeButtonA").width, y1 = getStyleSheet().getImage("closeButtonA").height;
188 		int x2 = position.width;
189 		int headerLength = cast(int)(extraButtons.length == 0 ? position.width() - 1 : position.width() - 1 -
190 				((extraButtons.length>>1) * x1));
191 		foreach(s ; extraButtons){
192 			x2 -= x1;
193 			output.insertBitmap(x2,0,getStyleSheet().getImage(s));
194 		}
195 		const ushort colorA = isActive ? getStyleSheet().getColor("WHAascent") : getStyleSheet().getColor("windowascent"),
196 				colorB = isActive ? getStyleSheet().getColor("WHAdescent") : getStyleSheet().getColor("windowdescent");
197 		output.drawLine(x1, headerLength, 0, 0, colorA);
198 		output.drawLine(x1, x1, 0, y1 - 1, colorA);
199 		output.drawLine(x1, headerLength, y1 - 1, y1 - 1, colorB);
200 		output.drawLine(headerLength, headerLength, 0, y1 - 1, colorB);
201 		output.drawColorText(x1,(y1-getStyleSheet().getFontset("default").getSize())/2,title,getStyleSheet().getFontset("default"),
202 				isActive ? getStyleSheet().getColor("WHTextActive") : getStyleSheet().getColor("WHTextInactive"), 0);
203 	}
204 	public @nogc @property bool active(){
205 		return isActive;
206 	}
207 	public @nogc @property bool active(bool val){
208 		return isActive = val;
209 	}
210 	public void setTitle(dstring s){
211 		title = s;
212 		drawHeader;
213 	}
214 	public dstring getTitle(){
215 		return title;
216 	}
217 	/**
218 	 * Detects where the mouse is clicked, then it either passes to an element, or tests whether the close button,
219 	 * an extra button was clicked, also tests for the header, which creates a drag event for moving the window.
220 	 */
221 	public void passMouseEvent(int x, int y, int state, ubyte button){
222 		if(state == ButtonState.PRESSED){
223 			if(getStyleSheet.getImage("closeButtonA").width > x && getStyleSheet.getImage("closeButtonA").height > y && button == MouseButton.LEFT){
224 				close();
225 				return;
226 			}else if(getStyleSheet.getImage("closeButtonA").height > y){
227 				if(y > position.width - (getStyleSheet.getImage("closeButtonA").width * extraButtons.length)){
228 					y -= position.width - (getStyleSheet.getImage("closeButtonA").width * extraButtons.length);
229 					extraButtonEvent(y / getStyleSheet.getImage("closeButtonA").width, button, state);
230 					return;
231 				}
232 				parent.moveUpdate(this);
233 				return;
234 			}
235 			//x -= position.xa;
236 			//y -= position.ya;
237 
238 			foreach(WindowElement e; mouseC){
239 				if(e.getPosition().left < x && e.getPosition().right > x && e.getPosition().top < y && e.getPosition().bottom > y){
240 					e.onClick(x - e.getPosition().left, y - e.getPosition().top, state, button);
241 					draggedElement = e;
242 
243 					return;
244 				}
245 			}
246 		}else{
247 			if(draggedElement){
248 				draggedElement.onClick(x - draggedElement.getPosition().left, y - draggedElement.getPosition().top, state, button);
249 				draggedElement = null;
250 			}
251 		}
252 	}
253 	/**
254 	 * Passes a mouseDragEvent if the user clicked on an element, held down the button, and moved the mouse.
255 	 */
256 	public void passMouseDragEvent(int x, int y, int relX, int relY, ubyte button){
257 		if(draggedElement){
258 			draggedElement.onDrag(x, y, relX, relY, button);
259 		}
260 	}
261 	/**
262 	 * Passes a mouseMotionEvent if the user moved the mouse.
263 	 */
264 	public void passMouseMotionEvent(int x, int y, int relX, int relY, ubyte button){
265 
266 	}
267 	/**
268 	 * Closes the window by calling the WindowHandler's closeWindow function.
269 	 */
270 	public void close(){
271 		parent.closeWindow(this);
272 	}
273 	/**
274 	 * Passes the scroll event to the element where the mouse pointer currently stands.
275 	 */
276 	public void passScrollEvent(int wX, int wY, int x, int y){
277 		foreach(WindowElement e; scrollC){
278 			if(e.getPosition().left < wX && e.getPosition().right > wX && e.getPosition().top < wX && e.getPosition().bottom > wY){
279 
280 				e.onScroll(x, y, wX, wY);
281 				return;
282 			}
283 		}
284 	}
285 	/**
286 	 * Called if an extra button was pressed.
287 	 */
288 	public void extraButtonEvent(int num, ubyte button, int state){
289 
290 	}
291 	/**
292 	 * Passes a keyboard event.
293 	 */
294 	public void passKeyboardEvent(wchar c, int type, int x, int y){
295 
296 	}
297 	/**
298 	 * Adds a WindowHandler to the window.
299 	 */
300 	public void addParent(IWindowHandler wh){
301 		parent = wh;
302 	}
303 	public void getFocus(){
304 
305 	}
306 	public void dropFocus(){
307 
308 	}
309 	public Coordinate getAbsolutePosition(WindowElement sender){
310 		return Coordinate(sender.position.left + position.left, sender.position.top + position.top, sender.position.right + position.right, sender.position.bottom + position.bottom);
311 	}
312 	/**
313 	* Moves the window to the exact location.
314 	*/
315 	public void move(int x, int y){
316 		parent.moveWindow(x, y, this);
317 		position.move(x,y);
318 	}
319 	/**
320 	* Moves the window by the given values.
321 	*/
322 	public void relMove(int x, int y){
323 		parent.relMoveWindow(x, y, this);
324 		position.relMove(x,y);
325 	}
326 	/**
327 	 * Sets the height of the window, also issues a redraw.
328 	 */
329 	public void setHeight(int y){
330 		position.bottom = position.top + y;
331 		draw();
332 		parent.refreshWindow(this);
333 	}
334 	/**
335 	 * Sets the width of the window, also issues a redraw.
336 	 */
337 	public void setWidth(int x){
338 		position.right = position.left + x;
339 		draw();
340 		parent.refreshWindow(this);
341 	}
342 	/**
343 	 * Sets the size of the window, also issues a redraw.
344 	 */
345 	public void setSize(int x, int y){
346 		position.right = position.left + x;
347 		position.bottom = position.top + y;
348 		draw();
349 		parent.refreshWindow(this);
350 	}
351 	/**
352 	 * Returns the outputted bitmap.
353 	 * Can be overridden for 32 bit outputs.
354 	 */
355 	public @property ABitmap getOutput(){
356 		return output.output;
357 	}
358 	/**
359 	 * Clears the background where the element is being drawn.
360 	 */
361 	public void clearArea(WindowElement sender){
362 		Coordinate c = sender.position;
363 		output.drawFilledRectangle(c.left, c.right, c.top, c.bottom, getStyleSheet.getColor("window"));
364 	}
365 }
366 
367 /**
368  * Standard text input form for various applications.
369  */
370 public class TextInputDialog : Window{
371 	//public ActionListener[] al;
372 	private TextBox textInput;
373 	private string source;
374 	public void function(dstring text) textOutput;
375 	/**
376 	 * Creates a TextInputDialog. Auto-sizing version is not implemented yet.
377 	 */
378 	public this(Coordinate size, string source, dstring title, dstring message, dstring text = ""){
379 		super(size, title);
380 		Label msg = new Label(message, "null", Coordinate(8, 20, size.width()-8, 39));
381 		addElement(msg, EventProperties.MOUSE);
382 
383 		textInput = new TextBox(text, "textInput", Coordinate(8, 40, size.width()-8, 59));
384 		addElement(textInput, EventProperties.MOUSE);
385 
386 		Button ok = new Button("Ok","ok", Coordinate(size.width()-48,65,size.width()-8,84));
387 		ok.onMouseLClickRel = &button_onClick;
388 		addElement(ok,EventProperties.MOUSE);
389 		this.source = source;
390 	}
391 
392 	public void button_onClick(Event ev){
393 		if(textOutput !is null){
394 			textOutput(textInput.getText);
395 		}
396 
397 		close();
398 
399 	}
400 }
401 /**
402  * Default dialog for simple messageboxes.
403  */
404 public class DefaultDialog : Window{
405 	private string source;
406 	public void delegate(Event ev) output;
407 
408 	public this(Coordinate size, string source, dstring title, dstring[] message, dstring[] options = ["Ok"],
409 			string[] values = ["close"]){
410 		this(size, title);
411 		//generate text
412 		this.source = source;
413 		int x1, x2, y1 = 20, y2 = getStyleSheet.drawParameters["TextSpacingTop"] + getStyleSheet.drawParameters["TextSpacingBottom"]
414 								+ getStyleSheet.getFontset(getStyleSheet.fontTypes["default"]).getSize;
415 		//Label msg = new Label(message[0], "null", Coordinate(5, 20, size.width()-5, 40));
416 		//addElement(msg, EventProperties.MOUSE);
417 
418 		//generate buttons
419 
420 		x1 = size.width() - 10;
421 		Button[] buttons;
422 		int button1 = size.height - getStyleSheet.drawParameters["WindowBottomPadding"];
423 		int button2 = button1 - getStyleSheet.drawParameters["ComponentHeight"];
424 		for(int i; i < options.length; i++){
425 			x2 = x1 - ((getStyleSheet().getFontset("default").getTextLength(options[i]) + 16));
426 			buttons ~= new Button(options[i], values[i], Coordinate(x2, button2, x1, button1));
427 			buttons[i].onMouseLClickRel = &actionEvent;
428 			addElement(buttons[i], EventProperties.MOUSE);
429 			x1 = x2;
430 		}
431 		//add labels
432 		for(int i; i < message.length; i++){
433 			Label msg = new Label(message[i], "null", Coordinate(getStyleSheet.drawParameters["WindowLeftPadding"],
434 								y1, size.width()-getStyleSheet.drawParameters["WindowRightPadding"], y1 + y2));
435 			addElement(msg, EventProperties.MOUSE);
436 			y1 += y2;
437 		}
438 	}
439 
440 	/*public this(string source, wstring title, wstring[] message, wstring[] options = ["Ok"], string[] values = ["ok"]){
441 		this(size, title);
442 	}*/
443 
444 	public this(Coordinate size, dstring title){
445 		super(size, title);
446 	}
447 	public void actionEvent(Event ev){
448 		if(ev.source == "close"){
449 			close();
450 		}else{
451 			if(output !is null){
452 				ev.subsource = source;
453 				output(ev);
454 			}
455 		}
456 	}
457 }
458 /**
459  * File dialog window for opening files.
460  * TODO: rewrite for new event handling system.
461  */
462 public class FileDialog : Window{
463 	/**
464 	 * Defines file association descriptions
465 	 */
466 	public struct FileAssociationDescriptor{
467 		public dstring description;		/// Describes the file type. Eg. "PPE map files"
468 		public string[] types;			/// The extensions associated with a given file format. Eg. ["*.htm","*.html"]. First is preferred one at saving.
469 		/**
470 		 * Creates a single FileAssociationDescriptor
471 		 */
472 		public this(dstring description, string[] types){
473 			this.description = description;
474 			this.types = types;
475 		}
476 		/**
477 		 * Returns the types as a single string.
478 		 */
479 		public dstring getTypesForSelector(){
480 			dstring result;
481 			foreach(string s ; types){
482 				result ~= to!dstring(s);
483 				result ~= ";";
484 			}
485 			result.length--;
486 			return result;
487 		}
488 	}
489 
490 	//private ActionListener al;
491 	private string source;
492 	private string[] pathList, driveList;
493 	private string directory, filename;
494 	private ListBox lb;
495 	private TextBox tb;
496 
497 	private bool save;
498 	private FileAssociationDescriptor[] filetypes;
499 	public static const string subsourceID = "filedialog";
500 	private int selectedType;
501 	public void delegate(Event ev) onFileselect;
502 
503 	/**
504 	 * Creates a file dialog with the given parameters.
505 	 * File types are given in the format '*.format', later implementations will enable file type descriptions.
506 	 */
507 	public this(dstring title, string source, void delegate(Event ev) onFileselect, FileAssociationDescriptor[] filetypes,
508 			string startDir, bool save = false, string filename = ""){
509 		this(Coordinate(20,20,240,198), title);
510 		this.source = source;
511 		this.filetypes = filetypes;
512 		this.save = save;
513 		this.onFileselect = onFileselect;
514 		//al = a;
515 		directory = startDir;
516 		//generate buttons
517 		Button[] buttons;
518 		buttons ~= new Button("Up","up",Coordinate(4, 154, 54, 174));
519 		buttons ~= new Button("Drive","drv",Coordinate(58, 154, 108, 174));
520 		if(save)
521 			buttons ~= new Button("Save","ok",Coordinate(112, 154, 162, 174));
522 		else
523 			buttons ~= new Button("Load","ok",Coordinate(112, 154, 162, 174));
524 		buttons ~= new Button("Close","close",Coordinate(166, 154, 216, 174));
525 		buttons ~= new Button("Type","type",Coordinate(166, 130, 216, 150));
526 		for(int i; i < buttons.length; i++){
527 			buttons[i].onMouseLClickRel = &actionEvent;
528 			addElement(buttons[i], EventProperties.MOUSE);
529 		}
530 		//generate textbox
531 		tb = new TextBox(to!dstring(filename), "filename", Coordinate(4, 130, 162, 150));
532 		//tb.addTextInputHandler(tih);
533 		tb.onTextInput = &actionEvent;
534 		addElement(tb, EventProperties.MOUSE);
535 		//generate listbox
536 
537 		//test parameters
538 
539 		//Date format: yyyy-mm-dd hh:mm:ss
540 		lb = new ListBox("lb", Coordinate(4, 20, 216, 126),null, new ListBoxHeader(["Name", "Type", "Date"], [160, 40, 176]) ,15);
541 
542 		addElement(lb, EventProperties.MOUSE | EventProperties.SCROLL);
543 		spanDir();
544 		//scrollC ~= lb;
545 		lb.onItemSelect = &actionEvent;
546 		detectDrive();
547 	}
548 
549 	public this(Coordinate size, dstring title){
550 		super(size, title);
551 	}
552 	/**
553 	 * Iterates throught a directory for listing.
554 	 */
555 	private void spanDir(){
556 		pathList.length = 0;
557 		ListBoxItem[] items;
558 		foreach(DirEntry de; dirEntries(directory, SpanMode.shallow)){
559 			if(de.isDir){
560 				pathList ~= de.name;
561 				/*columns[0].elements ~= to!wstring(getFilenameFromPath(de.name));
562 				columns[1].elements ~= "<DIR>";
563 				columns[2].elements ~= formatDate(de.timeLastModified);*/
564 				items ~= new ListBoxItem([to!dstring(getFilenameFromPath(de.name)),"<DIR>"d,formatDate(de.timeLastModified)]);
565 			}
566 		}
567 		//foreach(f; filetypes){
568 		foreach(ft; filetypes[selectedType].types){
569 			foreach(DirEntry de; dirEntries(directory, ft, SpanMode.shallow)){
570 				if(de.isFile){
571 					pathList ~= de.name;
572 					/*columns[0].elements ~= to!wstring(getFilenameFromPath(de.name, true));
573 					columns[1].elements ~= to!wstring(ft);
574 					columns[2].elements ~= formatDate(de.timeLastModified);*/
575 					items ~= new ListBoxItem([to!dstring(getFilenameFromPath(de.name)),to!dstring(ft),formatDate(de.timeLastModified)]);
576 				}
577 			}
578 		}
579 		lb.updateColumns(items);
580 		lb.draw();
581 
582 	}
583 	/**
584 	 * Standard date formatting tool.
585 	 */
586 	private dstring formatDate(SysTime time){
587 		dstring s;
588 		s ~= to!dstring(time.year());
589 		s ~= "-";
590 		s ~= to!dstring(time.month());
591 		s ~= "-";
592 		s ~= to!dstring(time.day());
593 		s ~= " ";
594 		s ~= to!dstring(time.hour());
595 		s ~= ":";
596 		s ~= to!dstring(time.minute());
597 		s ~= ":";
598 		s ~= to!dstring(time.second());
599 		return s;
600 	}
601 	/**
602 	 * Detects the available drives, currently only used under windows.
603 	 */
604 	private void detectDrive(){
605 		version(Windows){
606 			driveList.length = 0;
607 			for(char c = 'A'; c <='Z'; c++){
608 				string s;
609 				s ~= c;
610 				s ~= ":\x5c";
611 				if(exists(s)){
612 					driveList ~= (s);
613 				}
614 			}
615 		}else{
616 
617 		}
618 	}
619 	/**
620 	 * Returns the filename from the path.
621 	 */
622 	private string getFilenameFromPath(string p, bool b = false){
623 		size_t n, m = p.length;
624 		string s;
625 		for(size_t i ; i < p.length ; i++){
626 			if(std.path.isDirSeparator(p[i])){
627 				n = i;
628 			}
629 		}
630 		//n++;
631 		if(b){
632 			for(size_t i ; i < p.length ; i++){
633 				if(p[i] == '.'){
634 					m = i;
635 				}
636 			}
637 		}
638 		for( ; n < m ; n++){
639 			if(p[n] < 128 && p[n] > 31)
640 				s ~= p[n];
641 		}
642 		return s;
643 	}
644 	/**
645 	 * Called when the up button is pressed. Goes up in the folder hiearchy.
646 	 */
647 	private void up(){
648 		int n;
649 		for(int i ; i < directory.length ; i++){
650 			if(std.path.isDirSeparator(directory[i])){
651 				n = i;
652 			}
653 		}
654 		string newdir;
655 		for(int i ; i < n ; i++){
656 			newdir ~= directory[i];
657 		}
658 		directory = newdir;
659 		spanDir();
660 
661 	}
662 	/**
663 	 * Displays the drives. Under Linux, it goes into the /dev/ folder.
664 	 */
665 	private void changeDrive(){
666 		version(Windows){
667 			pathList.length = 0;
668 			ListBoxItem[] items;
669 			foreach(string drive; driveList){
670 				pathList ~= drive;
671 				items ~= new ListBoxItem([to!dstring(drive),"<DIR>","N/A"]);
672 			}
673 			lb.updateColumns(items);
674 			lb.draw();
675 		}else version(Posix){
676 			directory = "/dev/";
677 			spanDir();
678 		}
679 	}
680 	/**
681 	 * Creates an action event, then closes the window.
682 	 */
683 	private void fileEvent(){
684 		//wstring s = to!wstring(directory);
685 		filename = to!string(tb.getText);
686 		//al.actionEvent("file", EventType.FILEDIALOGEVENT, 0, s);
687 		if(onFileselect !is null)
688 			onFileselect(new Event(source, "filedialog", directory, filename, null, 0, EventType.FILEDIALOGEVENT));
689 		parent.closeWindow(this);
690 	}
691 
692 	public void actionEvent(Event event){
693 
694 		if(event.subsource == "fileSelector"){
695 			selectedType = event.value;
696 			spanDir();
697 		}
698 		switch(event.source){
699 			case "lb":
700 				try{
701 					if(pathList.length == 0) return;
702 					if(isDir(pathList[event.value])){
703 						directory = pathList[event.value];
704 						spanDir();
705 
706 
707 					}else{
708 						filename = getFilenameFromPath(pathList[event.value]);
709 						tb.setText(to!dstring(filename));
710 					}
711 				}catch(Exception e){
712 					DefaultDialog d = new DefaultDialog(Coordinate(10,10,256,80),"null",to!dstring("Error!"),
713 							PixelPerfectEngine.system.etc.stringArrayConv([e.msg]));
714 					parent.addWindow(d);
715 				}
716 				break;
717 			case "up": up(); break;
718 			case "drv": changeDrive(); break;
719 			case "ok": fileEvent(); break;
720 			case "close": parent.closeWindow(this); break;
721 			case "type":
722 				PopUpMenuElement[] e;
723 				for(int i ; i < filetypes.length ; i++){
724 					e ~= new PopUpMenuElement(to!string(i),filetypes[i].description, filetypes[i].getTypesForSelector());
725 				}
726 				PopUpMenu p = new PopUpMenu(e,"fileSelector");
727 				p.onMouseClick = &actionEvent;
728 				parent.addPopUpElement(p);
729 				break;
730 			default: break;
731 		}
732 	}
733 }
734 /**
735  * Handles windows as well as PopUpElements.
736  */
737 public class WindowHandler : InputListener, MouseListener, IWindowHandler{
738 	private Window[] windows;
739 	private PopUpElement[] popUpElements;
740 	private int numOfPopUpElements;
741 	private int[] priorities;
742 	public int screenX, screenY, rasterX, rasterY, moveX, moveY, mouseX, mouseY;
743 	//public Bitmap16Bit[wchar] basicFont, altFont, alarmFont;
744 	public StyleSheet defaultStyle;
745 	//public Bitmap16Bit[int] styleBrush;
746 	private ABitmap background;
747 	private ISpriteLayer spriteLayer;
748 	private bool moveState, dragEventState;
749 	private Window windowToMove, dragEventDest;
750 	private PopUpElement dragEventDestPopUp;
751 	private ubyte lastMouseButton;
752 
753 	public this(int sx, int sy, int rx, int ry,ISpriteLayer sl){
754 		screenX = sx;
755 		screenY = sy;
756 		rasterX = rx;
757 		rasterY = ry;
758 		spriteLayer = sl;
759 	}
760 
761 	public void addWindow(Window w){
762 		windows ~= w;
763 		w.addParent(this);
764 		//priorities ~= 666;
765 		w.draw();
766 		setWindowToTop(w);
767 
768 		/*for(int i ; i < windows.length ; i++){
769 			spriteLayer.addSprite(windows[i].output.output, i, windows[i].position);
770 		}*/
771 	}
772 
773 	/**
774 	 * Adds a DefaultDialog as a message box
775 	 */
776 	public void messageWindow(dstring title, dstring message, int width = 256){
777 		StyleSheet ss = getDefaultStyleSheet();
778 		dstring[] formattedMessage = ss.getFontset(ss.fontTypes["Label"]).breakTextIntoMultipleLines(message, width -
779 				ss.drawParameters["WindowLeftPadding"] - ss.drawParameters["WindowRightPadding"]);
780 		int height = cast(int)(formattedMessage.length * (ss.getFontset(ss.fontTypes["Label"]).getSize() +
781 				ss.drawParameters["TextSpacingTop"] + ss.drawParameters["TextSpacingBottom"]));
782 		height += ss.drawParameters["WindowTopPadding"] + ss.drawParameters["WindowBottomPadding"] +
783 				ss.drawParameters["ComponentHeight"];
784 		Coordinate c = Coordinate(mouseX - width / 2, mouseY - height / 2, mouseX + width / 2, mouseY + height / 2);
785 		addWindow(new DefaultDialog(c, null, title, formattedMessage));
786 	}
787 	public void addBackground(ABitmap b){
788 		background = b;
789 		spriteLayer.addSprite(background, 65536, 0, 0);
790 	}
791 
792 	private int whichWindow(Window w){
793 		for(int i ; i < windows.length ; i++){
794 			if(windows[i] == w){
795 				return i;
796 			}
797 		}
798 		return -1;
799 	}
800 
801 	public void setWindowToTop(Window sender){
802 		for(size_t s; s < windows.length; s++){
803 			if(windows[s] == sender){
804 				windows[0].active = false;
805 				windows[0].draw(true);
806 				Window ww = windows[s];
807 				ww.active = true;
808 				ww.draw(true);
809 				windows[s] = windows[0];
810 				windows[0] = ww;
811 				updateSpriteOrder();
812 				break;
813 			}
814 		}
815 	}
816 
817 	private void updateSpriteOrder(){
818 		for(int i ; i < windows.length ; i++){
819 			spriteLayer.removeSprite(i);
820 			spriteLayer.addSprite(windows[i].output.output, i, windows[i].position);
821 
822 		}
823 	}
824 
825 	/*public Bitmap16Bit[wchar] getFontSet(int style){
826 		switch(style){
827 			case 0: return basicFont;
828 			case 1: return altFont;
829 			case 3: return alarmFont;
830 			default: break;
831 		}
832 		return basicFont;
833 
834 	}*/
835 	public StyleSheet getStyleSheet(){
836 		return defaultStyle;
837 	}
838 	public void closeWindow(Window sender){
839 		//writeln(sender);
840 		dragEventState = false;
841 		dragEventDest = null;
842 		int p = whichWindow(sender);
843 		for(int i ; i < windows.length ; i++)
844 			spriteLayer.removeSprite(i);
845 		//spriteLayer.removeSprite(p);
846 		windows = remove(windows, p);
847 
848 		updateSpriteOrder();
849 	}
850 
851 	public void moveUpdate(Window sender){
852 		moveState = true;
853 		windowToMove = sender;
854 	}
855 	public void keyPressed(string ID, uint timestamp, uint devicenumber, uint devicetype){
856 
857 	}
858 	public void keyReleased(string ID, uint timestamp, uint devicenumber, uint devicetype){
859 
860 	}
861 	public void mouseButtonEvent(uint which, uint timestamp, uint windowID, ubyte button, ubyte state, ubyte clicks, int x, int y){
862 
863 		//converting the dimensions
864 		double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY);
865 		x = to!int(x * xR);
866 		y = to!int(y * yR);
867 		mouseX = x;
868 		mouseY = y;
869 		//if(button == MouseButton.LEFT){
870 		if(state == ButtonState.PRESSED){
871 			if(numOfPopUpElements < 0){
872 				foreach(p ; popUpElements){
873 				if(y >= p.coordinates.top && y <= p.coordinates.bottom && x >= p.coordinates.left && x <= p.coordinates.right){
874 					p.onClick(x - p.coordinates.left, y - p.coordinates.top);
875 					return;
876 				}
877 			}
878 			//removeAllPopUps();
879 			removeTopPopUp();
880 			}else{
881 				moveX = x;
882 				moveY = y;
883 				for(int i ; i < windows.length ; i++){
884 					if(x >= windows[i].position.left && x <= windows[i].position.right && y >= windows[i].position.top && y <= windows[i].position.bottom){
885 						//if(i == 0){
886 						dragEventState = true;
887 						windows[i].passMouseEvent(x - windows[i].position.left, y - windows[i].position.top, state, button);
888 						if(dragEventState)
889 							dragEventDest = windows[i];
890 					/*if(windows.length !=0){
891 						dragEventState = true;
892 						dragEventDest = windows[0];
893 					}*/
894 				//return;
895 					//}else{
896 						if(i != 0){
897 							setWindowToTop(windows[i]);
898 
899 						}
900 						lastMouseButton = button;
901 						return;
902 					}
903 				}
904 				passMouseEvent(x,y,state,button);
905 
906 			}
907 		}else{
908 			if(moveState){
909 				moveState = false;
910 			}else if(dragEventDest){
911 				dragEventDest.passMouseEvent(x - dragEventDest.position.left, y - dragEventDest.position.top, state, button);
912 				dragEventDest = null;
913 			}else{
914 				passMouseEvent(x,y,state,button);
915 			}
916 		}
917 	}
918 	public void passMouseEvent(int x, int y, int state, ubyte button){
919 
920 	}
921 	public void passMouseDragEvent(int x, int y, int relX, int relY, ubyte button){
922 	}
923 	public void passMouseMotionEvent(int x, int y, int relX, int relY, ubyte button){
924 	}
925 	public void mouseWheelEvent(uint type, uint timestamp, uint windowID, uint which, int x, int y, int wX, int wY){
926 		double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY);
927 		wX = to!int(wX * xR);
928 		wY = to!int(wY * yR);
929 		if(windows.length != 0)
930 			windows[0].passScrollEvent(wX - windows[0].position.left, wY - windows[0].position.top, y, x);
931 		passScrollEvent(wX,wY,x,y);
932 	}
933 	public void passScrollEvent(int wX, int wY, int x, int y){
934 
935 	}
936 	public void mouseMotionEvent(uint timestamp, uint windowID, uint which, uint state, int x, int y, int relX, int relY){
937 		//coordinate conversion
938 		double xR = to!double(rasterX) / to!double(screenX) , yR = to!double(rasterY) / to!double(screenY);
939 		x = to!int(x * xR);
940 		y = to!int(y * yR);
941 		relX = to!int(relX * xR);
942 		relY = to!int(relY * yR);
943 		//passing mouseMovementEvent onto PopUps
944 		if(numOfPopUpElements < 0){
945 			PopUpElement p = popUpElements[popUpElements.length - 1];
946 			if(p.coordinates.top < y && p.coordinates.bottom > y && p.coordinates.left < x && p.coordinates.right > x){
947 				p.onMouseMovement(x - p.coordinates.left, y - p.coordinates.top);
948 				return;
949 			}else{
950 				p.onMouseMovement(-1,-1);
951 			}
952 
953 		}
954 		if(state == ButtonState.PRESSED && moveState){
955 			windowToMove.relMove(relX, relY);
956 		}else if(state == ButtonState.PRESSED && dragEventDest){
957 			dragEventDest.passMouseDragEvent(x, y, relX, relY, lastMouseButton);
958 		}else{
959 			if(windows.length){
960 				windows[0].passMouseMotionEvent(x, y, relX, relY, lastMouseButton);
961 			}
962 		}
963 	}
964 	public void moveWindow(int x, int y, Window w){
965 		spriteLayer.relMoveSprite(whichWindow(w), x, y);
966 
967 	}
968 	public void refreshWindow(Window w){
969 		int n = whichWindow(w);
970 		spriteLayer.replaceSprite(windows[n].output.output, n, windows[n].position);
971 	}
972 	public void relMoveWindow(int x, int y, Window w){
973 		spriteLayer.relMoveSprite(whichWindow(w), x, y);
974 	}
975 	public void addPopUpElement(PopUpElement p){
976 		popUpElements ~= p;
977 		p.addParent(this);
978 		p.draw;
979 		mouseX -= (p.coordinates.width/2);
980 		mouseY -= (p.coordinates.height/2);
981 		p.coordinates.move(mouseX,mouseY);
982 		numOfPopUpElements--;
983 		spriteLayer.addSprite(p.output.output,numOfPopUpElements,mouseX,mouseY);
984 
985 	}
986 	public void addPopUpElement(PopUpElement p, int x, int y){
987 		popUpElements ~= p;
988 		p.addParent(this);
989 		p.draw;
990 		p.coordinates.move(x, y);
991 		numOfPopUpElements--;
992 		spriteLayer.addSprite(p.output.output,numOfPopUpElements, x, y);
993 	}
994 	private void removeAllPopUps(){
995 		for( ; numOfPopUpElements < 0 ; numOfPopUpElements++){
996 			spriteLayer.removeSprite(numOfPopUpElements);
997 		}
998 		popUpElements.length = 0;
999 	}
1000 	private void removeTopPopUp(){
1001 
1002 		spriteLayer.removeSprite(numOfPopUpElements++);
1003 
1004 		popUpElements.length--;
1005 	}
1006 	public StyleSheet getDefaultStyleSheet(){
1007 		return defaultStyle;
1008 	}
1009 	public void endPopUpSession(){
1010 		removeAllPopUps();
1011 	}
1012 	public void closePopUp(PopUpElement p){
1013 
1014 	}
1015 	public void drawUpdate(WindowElement sender){}
1016 	public void getFocus(WindowElement sender){}
1017 	public void dropFocus(WindowElement sender){}
1018 	public void drawUpdate(Window sender){
1019 		/*int p = whichWindow(sender);
1020 		spriteLayer.removeSprite(p);
1021 		spriteLayer.addSprite(sender.output.output,p,sender.position);*/
1022 	}
1023 	/*public Coordinate getAbsolutePosition(PopUpElement sender){
1024 		for(int i ; i < popUpElements.length ; i++){
1025 			if(popUpElements[i] = sender){
1026 
1027 			}
1028 		}
1029 		return Coordinate();
1030 	}*/
1031 }
1032 
1033 public interface IWindowHandler : PopUpHandler{
1034 	//public Bitmap16Bit[wchar] getFontSet(int style);
1035 	public StyleSheet getStyleSheet();
1036 	public void closeWindow(Window sender);
1037 	public void moveUpdate(Window sender);
1038 	public void setWindowToTop(Window sender);
1039 	public void addWindow(Window w);
1040 	public void refreshWindow(Window w);
1041 	public void moveWindow(int x, int y, Window w);
1042 	public void relMoveWindow(int x, int y, Window w);
1043 	public void drawUpdate(Window sender);
1044 	public void messageWindow(dstring title, dstring message, int width = 256);
1045 }
1046 
1047 public enum EventProperties : uint{
1048 	KEYBOARD		=	1,
1049 	MOUSE			=	2,
1050 	SCROLL			=	4
1051 }