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 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 		version(Windows){
103 			for(char c = 'A'; c <='Z'; c++){
104 				string s;
105 				s ~= c;
106 				s ~= ":\x5c";
107 				if(exists(s)){
108 					driveList ~= (s);
109 				}
110 			}
111 		}
112 
113 		spanDir();
114 	}
115 	///Ditto
116 	public this(dstring title, string source, void delegate(Event ev) onFileselect, FileAssociationDescriptor[] filetypes,
117 			string startDir, bool save = false, string filename = "", StyleSheet customStyle = null) {
118 		this.customStyle = customStyle;
119 		this(new Text(title, getStyleSheet().getChrFormatting("windowHeader")), source, onFileselect, filetypes, startDir, 
120 				save, filename, customStyle);
121 	}
122 	/**
123 	 * Iterates throught a directory for listing.
124 	 */
125 	private void spanDir(){
126 		pathList.length = 0;
127 		lw.clear();
128 		try {
129 			foreach(DirEntry de; dirEntries(directory, SpanMode.shallow)){
130 				if(de.isDir){
131 					pathList ~= de.name;
132 					createEntry(de.name, "<DIR>", de.timeLastModified);
133 				}
134 			}
135 
136 			foreach(ft; filetypes[selectedType].types){
137 				foreach(DirEntry de; dirEntries(directory, ft, SpanMode.shallow)){
138 					if(de.isFile){
139 						pathList ~= de.name;
140 						createEntry(de.name, ft, de.timeLastModified);
141 					}
142 				}
143 			}
144 		} catch (Exception e) {
145 			debug {
146 				import std.stdio : writeln;
147 				writeln(e);
148 			}
149 			handler.message("Directory error!", to!dstring(e.msg));
150 		}
151 		lw.refresh();
152 	}
153 	/**
154 	 * Creates a single ListViewItem with the supplied data, then adds it to the ListView.
155 	 */
156 	private void createEntry(string filename, string filetype, SysTime time) {
157 		import std.utf : toUTF32;
158 		auto frmt = getStyleSheet().getChrFormatting("ListViewItem");
159 		const int height = frmt.font.size + getStyleSheet().drawParameters["ListViewRowPadding"];
160 		lw ~= new ListViewItem(height, [new Text(toUTF32(baseName(filename)), frmt), new Text(toUTF32(filetype), frmt), 
161 				new Text(formatDate(time), frmt)]);
162 	}
163 	/**
164 	 * Creates drive entry for the ListView.
165 	 */
166 	private void createDriveEntry(dstring driveName) {
167 		//import std.utf : toUTF32;
168 		auto frmt = getStyleSheet().getChrFormatting("ListViewItem");
169 		const int height = frmt.font.size + getStyleSheet().drawParameters["ListViewRowPadding"];
170 		lw ~= new ListViewItem(height, [new Text(driveName, frmt), new Text("<DRIVE>", frmt), 
171 				new Text("n/a", frmt)]);
172 	}
173 	/**
174 	 * Standard date formatting tool.
175 	 */
176 	private dstring formatDate(SysTime time){
177 		dchar[] s;
178 		s.reserve(24);
179 		s ~= to!dstring(time.year());
180 		s ~= '-';
181 		s ~= to!dstring(time.month());
182 		s ~= '-';
183 		s ~= to!dstring(time.day());
184 		s ~= ' ';
185 		s ~= to!dstring(time.hour());
186 		s ~= ':';
187 		s ~= to!dstring(time.minute());
188 		s ~= ':';
189 		s ~= to!dstring(time.second());
190 		return s.idup;
191 	}
192 	/+
193 	/**
194 	 * Detects the available drives, currently only used under windows.
195 	 */
196 	private void detectDrive(){
197 		version(Windows){
198 			driveList.length = 0;
199 			for(char c = 'A'; c <='Z'; c++){
200 				string s;
201 				s ~= c;
202 				s ~= ":\x5c";
203 				if(exists(s)){
204 					driveList ~= (s);
205 				}
206 			}
207 		}
208 	}+/
209 	
210 	/**
211 	 * Called when the up button is pressed. Goes up in the folder hiearchy.
212 	 */
213 	private void up(Event ev){
214 		int n;
215 		for(int i ; i < directory.length ; i++){
216 			if(isDirSeparator(directory[i])){
217 				n = i;
218 			}
219 		}
220 		/+string newdir;
221 		for(int i ; i < n ; i++){
222 			newdir ~= directory[i];
223 		}+/
224 		//directory = newdir;
225 		directory = directory[0..n];
226 		spanDir();
227 
228 	}
229 	/**
230 	 * Displays the drives. Under Linux, it goes into the /dev/ folder.
231 	 */
232 	private void changeDrive(Event ev){
233 		version(Windows){
234 			pathList.length = 0;
235 			lw.clear();
236 			//ListBoxItem[] items;
237 			foreach(string drive; driveList){
238 				pathList ~= drive;
239 				//items ~= new ListBoxItem([to!dstring(drive),"<DRIVE>"d,""d]);
240 				createDriveEntry(to!dstring(drive));
241 			}
242 			lw.refresh();
243 			//lb.updateColumns(items);
244 			//lb.draw();
245 		}else version(Posix){
246 			directory = "/dev/";
247 			spanDir();
248 		}
249 	}
250 	/**
251 	 * Creates an action event, then closes the window.
252 	 */
253 	private void fileEvent(Event ev) {
254 		import std.utf : toUTF8;
255 		//wstring s = to!wstring(directory);
256 		filename = toUTF8(tb.getText.text);
257 		
258 		if(onFileselect !is null)
259 			onFileselect(new FileEvent(this, SourceType.DialogWindow, directory, filename, filetypes[selectedType].types[0][1..$]));
260 			//onFileselect(new Event(source, "", directory, filename, null, selectedType, EventType.FILEDIALOGEVENT));
261 		handler.closeWindow(this);
262 	}
263 	private void event_fileSelector(Event ev) {
264 		//selectedType = lw.value;
265 		import std.algorithm.searching : canFind;
266 		if (ev.type == EventType.Menu) {
267 			MenuEvent mev = cast(MenuEvent)ev;
268 			selectedType = cast(int)mev.itemNum;
269 			spanDir();
270 		}
271 	}
272 	private void button_type_onMouseLClickRel(Event ev) {
273 		PopUpMenuElement[] e;
274 		auto frmt1 = getStyleSheet().getChrFormatting("popUpMenu");
275 		auto frmt2 = getStyleSheet().getChrFormatting("popUpMenuSecondary");
276 		for(int i ; i < filetypes.length ; i++){
277 			e ~= new PopUpMenuElement(filetypes[i].types[0],new Text(filetypes[i].description, frmt1), 
278 					new Text(filetypes[i].getTypesForSelector(), frmt2));
279 		}
280 		PopUpMenu p = new PopUpMenu(e,"fileSelector", &event_fileSelector);
281 		handler.addPopUpElement(p);
282 	}
283 	private void button_close_onMouseLClickRel(Event ev) {
284 		handler.closeWindow(this);
285 	}
286 	private void listView_onItemSelect(Event ev) {
287 		try {
288 			if(pathList.length == 0) return;
289 			if(isDir(pathList[lw.value])){
290 				directory = pathList[lw.value];
291 				spanDir();
292 			}else{
293 				filename = baseName(stripExtension(pathList[lw.value]));
294 				tb.setText(new Text(to!dstring(filename), tb.getText().formatting));
295 			}
296 		} catch(Exception e) {
297 			handler.message("Error!",to!dstring(e.msg));
298 		}
299 	}
300 }