1 module PixelPerfectEngine.concrete.dialogs.filedialog;
2 
3 public import PixelPerfectEngine.concrete.window;
4 import PixelPerfectEngine.concrete.elements;
5 import std.datetime;
6 import std.conv : to;
7 import std.file;
8 static import std.path;
9 
10 /**
11  * File dialog window for opening files.
12  * Returns the selected filetype as an int value of the position of the types that were handled to the ctor.
13  */
14 public class FileDialog : Window {
15 	/**
16 	 * Defines file association descriptions
17 	 */
18 	public struct FileAssociationDescriptor {
19 		public dstring description;		/// Describes the file type. Eg. "PPE map files"
20 		public string[] types;			/// The extensions associated with a given file format. Eg. ["*.htm","*.html"]. First is preferred one at saving, if no filetype is described when typing.
21 		/**
22 		 * Creates a single FileAssociationDescriptor
23 		 */
24 		public this(dstring description, string[] types){
25 			this.description = description;
26 			this.types = types;
27 		}
28 		/**
29 		 * Returns the types as a single string.
30 		 */
31 		public dstring getTypesForSelector(){
32 			dstring result;
33 			foreach(string s ; types){
34 				result ~= to!dstring(s);
35 				result ~= ";";
36 			}
37 			result.length--;
38 			return result;
39 		}
40 	}
41 
42 	private string source;
43 	private string[] pathList, driveList;
44 	private string directory, filename;
45 	private ListView lw;
46 	private TextBox tb;
47 
48 	private bool save;
49 	private FileAssociationDescriptor[] filetypes;
50 	private int selectedType;
51 	public void delegate(Event ev) onFileselect;
52 	private Button button_up;
53 	private Button button_drv;
54 	private Button button_ok;
55 	private Button button_close;
56 	private Button button_type;
57 	public static dstring[] 	buttonTexts = ["Up", "Drive", "Save", "Load", "Close", "Type"];	///Can be changed for localization
58 	/**
59 	 * Creates a file dialog with the given parameters.
60 	 * File types are given in the format '*.format'.
61 	 */
62 	public this(Text title, string source, void delegate(Event ev) onFileselect, FileAssociationDescriptor[] filetypes,
63 			string startDir, bool save = false, string filename = "", StyleSheet customStyle = null) {
64 		super(Coordinate(20,20,240,198), title, null, customStyle);
65 		this.source = source;
66 		this.filetypes = filetypes;
67 		this.save = save;
68 		this.onFileselect = onFileselect;
69 		//al = a;
70 		directory = startDir;
71 		auto btnFrmt = getStyleSheet().getChrFormatting("button");
72 		button_up = new Button(new Text(buttonTexts[0], btnFrmt),"up",Coordinate(4, 154, 54, 174));
73 		button_up.onMouseLClick = &up;
74 		addElement(button_up);
75 		button_drv = new Button(new Text(buttonTexts[1], btnFrmt),"drv",Coordinate(58, 154, 108, 174));
76 		button_drv.onMouseLClick = &changeDrive;
77 		addElement(button_drv);
78 		button_ok = new Button(new Text((save ? buttonTexts[2] : buttonTexts[3]), btnFrmt),"ok",
79 				Coordinate(112, 154, 162, 174));
80 		button_ok.onMouseLClick = &fileEvent;
81 		addElement(button_ok);
82 		button_close = new Button(new Text(buttonTexts[4], btnFrmt),"close",Coordinate(166, 154, 216, 174));
83 		button_close.onMouseLClick = &button_close_onMouseLClickRel;
84 		addElement(button_close);
85 		button_type = new Button(new Text(buttonTexts[5], btnFrmt),"type",Coordinate(166, 130, 216, 150));
86 		button_type.onMouseLClick = &button_type_onMouseLClickRel;
87 		addElement(button_type);
88 		//generate textbox
89 		tb = new TextBox(new Text(to!dstring(filename), getStyleSheet().getChrFormatting("textBox")), "filename", 
90 				Coordinate(4, 130, 162, 150));
91 		addElement(tb);
92 
93 		//generate listview
94 		auto hdrFrmt = getStyleSheet().getChrFormatting("ListViewHeader");
95 		const int headerHeight = hdrFrmt.font.size + getStyleSheet().drawParameters["ListViewRowPadding"];
96 		ListViewHeader lvh = new ListViewHeader(headerHeight, [160, 40, 176], [new Text("Name", hdrFrmt), 
97 				new Text("Type", hdrFrmt), new Text("Date", hdrFrmt)]);
98 		lw = new ListView(lvh, null, "lw", Box(4, 20, 216, 126));
99 		addElement(lw);
100 		lw.onItemSelect = &listView_onItemSelect;
101 
102 		spanDir();
103 
104 		detectDrive();
105 	}
106 	///Ditto
107 	public this(dstring title, string source, void delegate(Event ev) onFileselect, FileAssociationDescriptor[] filetypes,
108 			string startDir, bool save = false, string filename = "", StyleSheet customStyle = null) {
109 		this.customStyle = customStyle;
110 		this(new Text(title, getStyleSheet().getChrFormatting("windowHeader")), source, onFileselect, filetypes, startDir, 
111 				save, filename, customStyle);
112 	}
113 	/**
114 	 * Iterates throught a directory for listing.
115 	 */
116 	private void spanDir(){
117 		pathList.length = 0;
118 		lw.clear();
119 		
120 		foreach(DirEntry de; dirEntries(directory, SpanMode.shallow)){
121 			if(de.isDir){
122 				pathList ~= de.name;
123 				createEntry(de.name, "<DIR>", de.timeLastModified);
124 			}
125 		}
126 		
127 		foreach(ft; filetypes[selectedType].types){
128 			foreach(DirEntry de; dirEntries(directory, ft, SpanMode.shallow)){
129 				if(de.isFile){
130 					pathList ~= de.name;
131 					createEntry(de.name, ft, de.timeLastModified);
132 				}
133 			}
134 		}
135 		
136 		lw.refresh();
137 	}
138 	/**
139 	 * Creates a single ListViewItem with the supplied data, then adds it to the ListView.
140 	 */
141 	private void createEntry(string filename, string filetype, SysTime time) {
142 		import std.utf : toUTF32;
143 		auto frmt = getStyleSheet().getChrFormatting("ListViewItem");
144 		const int height = frmt.font.size + getStyleSheet().drawParameters["ListViewRowPadding"];
145 		lw ~= new ListViewItem(height, [new Text(toUTF32(filename), frmt), new Text(toUTF32(filetype), frmt), 
146 				new Text(formatDate(time), frmt)]);
147 	}
148 	/**
149 	 * Creates drive entry for the ListView.
150 	 */
151 	private void createDriveEntry(dstring driveName) {
152 		//import std.utf : toUTF32;
153 		auto frmt = getStyleSheet().getChrFormatting("ListViewItem");
154 		const int height = frmt.font.size + getStyleSheet().drawParameters["ListViewRowPadding"];
155 		lw ~= new ListViewItem(height, [new Text(driveName, frmt), new Text("<DRIVE>", frmt), 
156 				new Text("n/a", frmt)]);
157 	}
158 	/**
159 	 * Standard date formatting tool.
160 	 */
161 	private dstring formatDate(SysTime time){
162 		dchar[] s;
163 		s.reserve(24);
164 		s ~= to!dstring(time.year());
165 		s ~= '-';
166 		s ~= to!dstring(time.month());
167 		s ~= '-';
168 		s ~= to!dstring(time.day());
169 		s ~= ' ';
170 		s ~= to!dstring(time.hour());
171 		s ~= ':';
172 		s ~= to!dstring(time.minute());
173 		s ~= ':';
174 		s ~= to!dstring(time.second());
175 		return s.idup;
176 	}
177 	/**
178 	 * Detects the available drives, currently only used under windows.
179 	 */
180 	private void detectDrive(){
181 		version(Windows){
182 			driveList.length = 0;
183 			for(char c = 'A'; c <='Z'; c++){
184 				string s;
185 				s ~= c;
186 				s ~= ":\x5c";
187 				if(exists(s)){
188 					driveList ~= (s);
189 				}
190 			}
191 		}else{
192 
193 		}
194 	}
195 	/**
196 	 * Returns the filename from the path.
197 	 */
198 	private string getFilenameFromPath(string p, bool b = false){
199 		size_t n, m = p.length;
200 		string s;
201 		for(size_t i ; i < p.length ; i++){
202 			if(std.path.isDirSeparator(p[i])){
203 				n = i;
204 			}
205 		}
206 		//n++;
207 		if(b){
208 			for(size_t i ; i < p.length ; i++){
209 				if(p[i] == '.'){
210 					m = i;
211 				}
212 			}
213 		}
214 		for( ; n < m ; n++){
215 			if(p[n] < 128 && p[n] > 31)
216 				s ~= p[n];
217 		}
218 		return s;
219 	}
220 	/**
221 	 * Called when the up button is pressed. Goes up in the folder hiearchy.
222 	 */
223 	private void up(Event ev){
224 		int n;
225 		for(int i ; i < directory.length ; i++){
226 			if(std.path.isDirSeparator(directory[i])){
227 				n = i;
228 			}
229 		}
230 		/+string newdir;
231 		for(int i ; i < n ; i++){
232 			newdir ~= directory[i];
233 		}+/
234 		//directory = newdir;
235 		directory = directory[0..n];
236 		spanDir();
237 
238 	}
239 	/**
240 	 * Displays the drives. Under Linux, it goes into the /dev/ folder.
241 	 */
242 	private void changeDrive(Event ev){
243 		version(Windows){
244 			pathList.length = 0;
245 			//ListBoxItem[] items;
246 			foreach(string drive; driveList){
247 				pathList ~= drive;
248 				//items ~= new ListBoxItem([to!dstring(drive),"<DRIVE>"d,""d]);
249 				createDriveEntry(to!dstring(drive));
250 			}
251 			//lb.updateColumns(items);
252 			//lb.draw();
253 		}else version(Posix){
254 			directory = "/dev/";
255 			spanDir();
256 		}
257 	}
258 	/**
259 	 * Creates an action event, then closes the window.
260 	 */
261 	private void fileEvent(Event ev) {
262 		import std.utf : toUTF8;
263 		//wstring s = to!wstring(directory);
264 		filename = toUTF8(tb.getText.text);
265 		
266 		if(onFileselect !is null)
267 			onFileselect(new FileEvent(this, SourceType.DialogWindow, directory, filename, filetypes[selectedType].types[0][1..$]));
268 			//onFileselect(new Event(source, "", directory, filename, null, selectedType, EventType.FILEDIALOGEVENT));
269 		handler.closeWindow(this);
270 	}
271 	private void event_fileSelector(Event ev) {
272 		//selectedType = lw.value;
273 		import std.algorithm.searching : canFind;
274 		if (ev.type == EventType.Menu) {
275 			MenuEvent mev = cast(MenuEvent)ev;
276 			selectedType = cast(int)mev.itemNum;
277 			spanDir();
278 		}
279 	}
280 	private void button_type_onMouseLClickRel(Event ev) {
281 		PopUpMenuElement[] e;
282 		auto frmt1 = getStyleSheet().getChrFormatting("menuPri");
283 		auto frmt2 = getStyleSheet().getChrFormatting("menuSec");
284 		for(int i ; i < filetypes.length ; i++){
285 			e ~= new PopUpMenuElement(filetypes[i].types[0],new Text(filetypes[i].description, frmt1), 
286 					new Text(filetypes[i].getTypesForSelector(), frmt2));
287 		}
288 		PopUpMenu p = new PopUpMenu(e,"fileSelector", &event_fileSelector);
289 		handler.addPopUpElement(p);
290 	}
291 	private void button_close_onMouseLClickRel(Event ev) {
292 		handler.closeWindow(this);
293 	}
294 	private void listView_onItemSelect(Event ev) {
295 		try{
296 			if(pathList.length == 0) return;
297 			if(isDir(pathList[lw.value])){
298 				directory = pathList[lw.value];
299 				spanDir();
300 			}else{
301 				filename = getFilenameFromPath(pathList[lw.value]);
302 				tb.setText(new Text(to!dstring(filename), tb.getText().formatting));
303 			}
304 		}catch(Exception e){
305 			import PixelPerfectEngine.concrete.dialogs.defaultdialog;
306 			auto frmt1 = getStyleSheet().getChrFormatting("windowHeader");
307 			auto frmt2 = getStyleSheet().getChrFormatting("default");
308 			DefaultDialog d = new DefaultDialog(Coordinate(10,10,256,80),"null", new Text(to!dstring("Error!"), frmt1),
309 					[new Text(to!dstring(e.msg), frmt2)]);
310 			handler.addWindow(d);
311 		}
312 	}
313 }