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