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 }