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 }