ecere/gui/dialogs/FileDialog: (#845) Fixed Select Directory dialog intuitiveness...
[sdk] / ecere / src / gui / dialogs / FileDialog.ec
1 namespace gui::dialogs;
2
3 //#ifdef __WIN32__  // We want the strings in the .pot when building on Unix
4 static define rootName = $"Entire Computer";
5 static define msNetwork = $"Microsoft Windows Network";
6 //#endif
7
8 import "Window"
9
10 #define IS_ALUNDER(ch) ((ch) == '_' || isalnum((ch)))
11
12 default:
13 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
14 private:
15
16 static const char * iconNames[] =
17 {
18    "<:ecere>places/folder.png",
19    "<:ecere>status/folderOpen.png",
20    "<:ecere>devices/computer.png",
21    "<:ecere>devices/driveHardDisk.png",
22    "<:ecere>places/driveRemote.png",
23    "<:ecere>devices/mediaOptical.png",
24    "<:ecere>devices/driveRemovableMedia.png",
25    "<:ecere>devices/mediaFloppy.png",
26    "<:ecere>places/networkWorkgroup.png",
27    "<:ecere>places/networkServer.png",
28    "<:ecere>places/folderRemote.png",
29
30    "<:ecere>mimeTypes/file.png",                   /*normalFile*/
31    "<:ecere>mimeTypes/textEcereWorkspace.png",     /*ewsFile*/
32    "<:ecere>mimeTypes/textEcereProject.png",       /*epjFile*/
33    "<:ecere>mimeTypes/textEcereSource.png",        /*ecFile*/
34    "<:ecere>mimeTypes/textEcereHeader.png",        /*ehFile*/
35    "<:ecere>mimeTypes/textCSource.png",            /*cFile*/
36    "<:ecere>mimeTypes/textCHeader.png",            /*hFile*/
37    "<:ecere>mimeTypes/textC++Source.png",          /*cppFile*/
38    "<:ecere>mimeTypes/textC++Header.png",          /*hppFile*/
39    "<:ecere>mimeTypes/text.png",                   /*textFile*/
40    "<:ecere>mimeTypes/textHyperTextMarkup.png",    /*webFile*/
41    "<:ecere>mimeTypes/image.png",                  /*pictureFile*/
42    "<:ecere>status/audioVolumeHigh.png",           /*soundFile*/
43    "<:ecere>mimeTypes/package.png",                /*archiveFile*/
44    "<:ecere>mimeTypes/packageSoftware.png",        /*packageFile*/
45    "<:ecere>mimeTypes/packageOpticalDisc.png"      /*opticalMediaImageFile*/
46 };
47
48 public enum FileNameType     // Had to be private, since icons member of FileDialog is also private... (Static members? Not for a while...)
49 {
50    folder, folderOpen, computer,
51    drive, netDrive, cdrom, removable, floppy, network, server, share, // these are sort equal
52
53    normalFile, ewsFile, epjFile, ecFile, ehFile, cFile, hFile, cppFile, hppFile,
54    textFile, webFile, pictureFile, soundFile,
55    archiveFile, packageFile, opticalMediaImageFile; /* these (all previous) are sort equal */
56
57    /*property char *
58    {
59       set
60       {
61          this = SelectByExtension(value);
62       }
63    }*/
64    public property bool isFolderType
65    {
66       get { return this >= folder && this <= share; }
67    }
68
69    public property bool isFileType
70    {
71       get { return this >= normalFile && this <= opticalMediaImageFile; }
72    }
73
74    FileNameType ::SelectByExtension(const char * extension)
75    {
76       if(!strcmpi(extension, "ews"))
77          return ewsFile;
78       else if(!strcmpi(extension, "epj"))
79          return epjFile;
80       else if(!strcmpi(extension, "ec"))
81          return ecFile;
82       else if(!strcmpi(extension, "eh"))
83          return ehFile;
84       else if(!strcmpi(extension, "cpp") ||
85             !strcmpi(extension, "cc") || !strcmpi(extension, "cxx"))
86          return cppFile;
87       else if(!strcmpi(extension, "hpp") ||
88             !strcmpi(extension, "hh") || !strcmpi(extension, "hxx"))
89          return hppFile;
90       else if(!strcmpi(extension, "c"))
91          return cFile;
92       else if(!strcmpi(extension, "h"))
93          return hFile;
94       else if(!strcmpi(extension, "txt") || !strcmpi(extension, "text") ||
95             !strcmpi(extension, "nfo") || !strcmpi(extension, "info"))
96          return textFile;
97       else if(!strcmpi(extension, "htm") || !strcmpi(extension, "html") ||
98             !strcmpi(extension, "css") || !strcmpi(extension, "php") ||
99             !strcmpi(extension, "js"))
100          return webFile;
101       else if(!strcmpi(extension, "bmp") || !strcmpi(extension, "pcx") ||
102             !strcmpi(extension, "jpg") || !strcmpi(extension, "jpeg") ||
103             !strcmpi(extension, "gif") || !strcmpi(extension, "png") ||
104             !strcmpi(extension, "ico"))
105          return pictureFile;
106       else if(!strcmpi(extension, "wav") || !strcmpi(extension, "mp3") ||
107             !strcmpi(extension, "ogg") || !strcmpi(extension, "snd"))
108          return soundFile;
109       else if(!strcmpi(extension, "ear") || !strcmpi(extension, "7z") ||
110             !strcmpi(extension, "rar") || !strcmpi(extension, "zip") ||
111             !strcmpi(extension, "gz") || !strcmpi(extension, "bz2") ||
112             !strcmpi(extension, "tar") || !strcmpi(extension, "arj") ||
113             !strcmpi(extension, "lza") || !strcmpi(extension, "lzh") ||
114             !strcmpi(extension, "cpio") || !strcmpi(extension, "z"))
115          return archiveFile;
116       else if(!strcmpi(extension, "cab") || !strcmpi(extension, "deb") ||
117             !strcmpi(extension, "rpm"))
118          return packageFile;
119       else if(!strcmpi(extension, "iso") || !strcmpi(extension, "mds") ||
120             !strcmpi(extension, "cue") || !strcmpi(extension, "bin") ||
121             !strcmpi(extension, "ccd") || !strcmpi(extension, "bwt") ||
122             !strcmpi(extension, "cdi") || !strcmpi(extension, "nrg"))
123          return opticalMediaImageFile;
124       return normalFile;
125    }
126 };
127
128 public enum FileDialogType { open, save, selectDir, multiOpen };
129 public struct FileFilter
130 {
131    const char * name;
132    const char * extensions;
133
134    bool ValidateFileName(const char * name)
135    {
136       if(strcmp(name, "..") && strcmp(name, ".") && strcmp(name, ""))
137       {
138          if(extensions) // && !stats.attribs.isDirectory)
139          {
140             char extension[MAX_EXTENSION], compared[MAX_EXTENSION];
141             int c;
142
143             GetExtension(name, extension);
144             for(c = 0; extensions[c];)
145             {
146                int len = 0;
147                char ch;
148                for(;(ch = extensions[c]) && !IS_ALUNDER(ch); c++);
149                for(;(ch = extensions[c]) &&  IS_ALUNDER(ch); c++)
150                   compared[len++] = ch;
151                compared[len] = '\0';
152
153                if(!strcmpi(extension, compared))
154                   return true;
155             }
156          }
157          else
158             return true;
159       }
160       return false;
161    }
162 };
163 public enum FileForceExtension { never, always, whenNoneGiven };
164 public struct FileType
165 {
166    const char * name, * typeExtension;
167    FileForceExtension forceExtension;
168 };
169
170 static enum FileDialogSelectFrom { fromEditBox, fromListBox, fromDropBox };
171
172 public struct FileName
173 {
174    const char * name;
175    FileNameType type;
176    int indent;
177
178    void OnDisplay(Surface surface, int x, int y, int width, FileDialog fileDialog, Alignment alignment, DataDisplayFlags displayFlags)
179    {
180       int indentSize = (displayFlags.dropBox) ? 0 : 10;
181       int textOffset;
182
183       Bitmap icon = fileDialog.icons[type].bitmap;
184       if(!icon && type > normalFile)
185          icon = fileDialog.icons[normalFile].bitmap;
186       if(!icon)
187       {
188          if(type == folder || type == folderOpen)
189             surface.SetForeground(red); //Color { 170, 170, 0 } // REDJ What is that color?
190          indentSize = 8;
191       }
192       textOffset = indent * indentSize + (icon ? (icon.width + 4) : 0);
193
194       surface.WriteTextDots
195          (alignment, x + textOffset, y + 2, width - textOffset, name, strlen(name));
196       if(icon)
197          surface.Blit(icon, x + indent * indentSize, y,0,0, icon.width, icon.height);
198    }
199
200    int OnCompare(FileName b)
201    {
202       int result = 0;
203       if(type == b.type || (type >= normalFile && b.type >= normalFile) || (type >= drive && type <= share))
204          result = strcmpi(name, b.name);
205       else
206       {
207          if(type == folder && b.type >= normalFile) result = -1;
208          else if(type >= normalFile && b.type == folder) result = 1;
209       }
210       return result;
211    }
212
213    void OnCopy(FileName newData)
214    {
215       type = newData.type;
216       indent = newData.indent;
217       if(newData.name)
218       {
219          int len = strlen(newData.name) + 1;
220          name = new char[len];
221          CopyBytes((char *)name, newData.name, len);
222       }
223    }
224
225    bool OnGetDataFromString(const char * string)
226    {
227       int len = strlen(string) + 1;
228       name = new char[len];
229       CopyBytes((char *)name, string, len);
230       return true;
231    }
232
233    void OnFree()
234    {
235       char * n = (char *)name;
236       delete n;
237       name = null;
238    }
239
240    const char * OnGetString(char * string, void * fieldData, bool * needClass)
241    {
242       return name;
243    }
244 };
245
246 public class FileDialog : Window
247 {
248    text = $"Select a file...";
249    background = formColor;
250    hasClose = true;
251    borderStyle = sizable;
252    tabCycle = true;
253    autoCreate = false;
254    minClientSize = { 500, 300 };
255
256 public:
257    property FileDialogType type
258    {
259       get { return style; }
260       set
261       {
262          int numTypes = sizeTypes / sizeof(FileType);
263          int rightOffset = (value != selectDir && numTypes > 0) ? 48 : 16;
264
265          style = value;
266
267          if(value == selectDir)
268          {
269             // Filters
270             filter.visible = false;
271             filterLabel.visible = false;
272             // Types
273             type.visible = false;
274             typeLabel.visible = false;
275
276             open.visible = true;
277             open.isDefault = true;
278
279             ok.text = $"Select";
280             ok.id = DialogResult::ok;
281             ok.hotKey = altS;
282             ok.isDefault = false;
283          }
284          else
285          {
286             // Filters
287             filter.visible = true;
288             filterLabel.visible = true;
289
290             // Types
291             type.visible = numTypes != 0;
292             typeLabel.visible = numTypes != 0;
293
294             open.visible = false;
295             open.isDefault = false;
296
297             ok.text = $"OK";
298             ok.id = 0;
299             ok.hotKey = 0;
300             ok.isDefault = true;
301          }
302          ok.anchor = { right = 10, bottom = 32 + rightOffset - 1 };
303
304          // Cancel Button
305          cancel.anchor = { right = 10, bottom = rightOffset - 1 };
306
307          listBox.anchor = { left = 8, right = 8, top = 40, bottom = 64 + rightOffset };
308          fileName.anchor = { left = 96, bottom = 32 + rightOffset, right = 104 };
309          fileNameLabel.anchor = { left = 8, bottom = 35 + rightOffset };
310
311          listBox.multiSelect = value == multiOpen;
312          fileName.text = (value == selectDir) ? $"Directory:" : $"File Name:";
313       }
314    };
315
316    // Stuff currently in config moving to FileDialog:
317    property const char * filePath { set { strcpy(filePath, value); } get { return (char *)filePath; } };
318    property const char * currentDirectory
319    {
320       set
321       {
322          GetWorkingDir(currentDirectory, MAX_DIRECTORY);
323          PathCat(currentDirectory, value);
324          FileFixCase(currentDirectory);
325       }
326       get { return currentDirectory; }
327    };
328    property FileFilter * filters { set { filters = value; } get { return filters; } };
329    property FileType * types { set { types = value; } get { return types; } };
330
331    // Replace with Array system
332    property int sizeFilters
333    {
334       set
335       {
336          int numFilters = value / sizeof(FileFilter);
337          int c;
338
339          sizeFilters = value;
340
341          // File Extension Filter
342          filter.Clear();
343          // filter.AddField(null);
344          if(filters)
345          {
346             for(c = 0; c<numFilters; c++)
347             {
348                DataRow row = filter.AddString(filters[c].name);
349                row.tag = c;
350             }
351          }
352          if(!numFilters)
353             filter.AddString($"All files");
354
355          if(fileFilter >= numFilters) fileFilter = 0;
356          filter.currentRow = filter.FindRow(fileFilter);
357       }
358       get { return sizeFilters; }
359    };
360    property int sizeTypes
361    {
362       set
363       {
364          int numTypes = value / sizeof(FileType);
365          int rightOffset = (numTypes > 0) ? 48 : 16;
366
367          sizeTypes = value;
368
369          // Filters
370          filter.anchor = { left = 96, right = 104, bottom = rightOffset };
371          filterLabel.anchor = { left = 8, bottom = 3 + rightOffset };
372
373          if(style != selectDir)
374          {
375             // Types
376             type.visible = numTypes ? true : false;
377             typeLabel.visible = numTypes ? true : false;
378
379             // Ok Button
380             ok.anchor = { right = 10, bottom = 32 + rightOffset - 1 };
381
382             // Cancel Button
383             cancel.anchor = { right = 10, bottom = rightOffset - 1 };
384          }
385
386          listBox.anchor = { left = 8, right = 8, top = 40, bottom = 64 + rightOffset };
387          fileName.anchor = { left = 96, bottom = 32 + rightOffset, right = 104 };
388          fileNameLabel.anchor = { left = 8, bottom = 35 + rightOffset };
389
390          // File Types
391          if(numTypes)
392          {
393             int c;
394
395             type.Clear();
396             // type.AddField(null);
397             if(types)
398             {
399                for(c = 0; c<numTypes; c++)
400                {
401                   DataRow row = type.AddString(types[c].name);
402                   row.tag = c;
403                }
404             }
405             if(fileType >= numTypes) fileType = 0;
406
407             type.currentRow = type.FindRow(fileType);
408          }
409       }
410       get { return sizeTypes; }
411    };
412
413    property int filter { set { fileFilter = value; } get { return fileFilter; } };
414    property int fileType { set { fileType = value; } get { return fileType; } };
415    property bool mayNotExist { set { mayNotExist = value; } get { return mayNotExist; } };
416
417    // Get only
418    property int numSelections { get { return numSelections; } };
419    property const char * const * multiFilePaths { get { return (const char * const *)multiFilePaths; } };
420
421 private:
422    FileDialog()
423    {
424       FileNameType c;
425
426       lookIn.AddField(lookInField);
427       listBox.AddField(nameField);
428       listBox.AddField(typeField);
429       listBox.AddField(sizeField);
430
431       filter.currentRow = filter.AddString($"All files");
432
433       GetWorkingDir(currentDirectory, MAX_DIRECTORY);
434       FileFixCase(currentDirectory);
435
436       // Resources
437       for(c = 0; c<FileNameType::enumSize; c++)
438       {
439          icons[c] = BitmapResource { iconNames[c], alphaBlend = true };
440          AddResource(icons[c]);
441       }
442    }
443
444    ~FileDialog()
445    {
446       char * customExtensions = (char *)customFilter.extensions;
447       delete customExtensions;
448       customFilter.extensions = null;
449       if(multiFilePaths)
450       {
451          int c;
452          for(c = 0; c<numSelections; c++)
453             delete multiFilePaths[c];
454          delete multiFilePaths;
455       }
456    }
457
458    void ListDrives()
459    {
460       int start = 0;
461       char tmpDir[MAX_FILENAME];
462       DataRow row;
463       FileName fileName;
464 #ifdef __WIN32__
465       FileListing listing { "/" };
466 #endif
467       int c;
468
469       fileName.indent = 0;
470
471       // Fill the path dropbox
472       lookIn.Clear();
473
474       row = lookIn.AddRow();
475
476    #ifdef __WIN32__
477       fileName.name = rootName;
478    #else
479       fileName.name = "/";
480    #endif
481       fileName.type = computer;
482       row.SetData(null, fileName);
483       lookIn.currentRow = row;
484
485       start = 0;
486    #ifdef __WIN32__
487       while(listing.Find())
488       {
489          row = lookIn.AddRow();
490
491          fileName.name = listing.name;
492          fileName.type = drive;
493          if(listing.stats.attribs.isCDROM) fileName.type = cdrom;
494          if(listing.stats.attribs.isRemote) fileName.type = netDrive;
495          if(listing.stats.attribs.isRemovable)
496          {
497             if(listing.name[0] == 'A' || listing.name[0] == 'B')
498                fileName.type = floppy;
499             else
500                fileName.type = removable;
501          }
502
503          fileName.indent = 1;
504          row.SetData(null, fileName);
505
506          start = 2;
507          if(listing.name[0] == currentDirectory[0] &&
508             listing.name[1] == currentDirectory[1])
509          {
510    #endif
511             for(c = start; currentDirectory[c]; )
512             {
513                int len = 0;
514                char ch;
515                for(;(ch = currentDirectory[c]) && (ch == '/' || ch == '\\'); c++);
516                for(;(ch = currentDirectory[c]) && (ch != '/' && ch != '\\'); c++)
517                {
518                   if(len < MAX_FILENAME)
519                      tmpDir[len++] = ch;
520                }
521                for(;(ch = currentDirectory[c]) && (ch == '/' || ch == '\\'); c++);
522                tmpDir[len] = '\0';
523
524                if(len > 0)
525                {
526                   row = lookIn.AddRow();
527                   fileName.name = tmpDir;
528                   fileName.type = ch ? folder : folderOpen;
529                   fileName.indent++;
530                   row.SetData(null, fileName);
531                }
532                if(!ch)
533                   lookIn.currentRow = row;
534             }
535    #ifdef __WIN32__
536             if(c == start)
537                lookIn.currentRow = row;
538          }
539          c++;
540       }
541
542       row = lookIn.AddRow();
543       fileName.name = msNetwork;
544       fileName.type = network;
545       fileName.indent = 0;
546       row.SetData(null, fileName);
547       if(!strcmp(currentDirectory, "\\\\"))
548          lookIn.currentRow = row;
549       else
550       {
551          if(currentDirectory[0] == '\\' && currentDirectory[1] == '\\')
552          {
553             for(c = 2; currentDirectory[c]; )
554             {
555                int len = 0;
556                char ch;
557                for(;(ch = currentDirectory[c]) && (ch == '/' || ch == '\\'); c++);
558                for(;(ch = currentDirectory[c]) && (ch != '/' && ch != '\\'); c++)
559                {
560                   if(len < MAX_FILENAME)
561                      tmpDir[len++] = ch;
562                }
563                for(;(ch = currentDirectory[c]) && (ch == '/' || ch == '\\'); c++);
564                tmpDir[len] = '\0';
565
566                if(len > 0)
567                {
568                   row = lookIn.AddRow();
569                   fileName.name = tmpDir;
570                   fileName.indent++;
571                   if(fileName.indent == 1)
572                      fileName.type = server;
573                   else if(fileName.indent == 2)
574                      fileName.type = share;
575                   else
576                      fileName.type = ch ? folder : folderOpen;
577                   row.SetData(null, fileName);
578                }
579                if(!ch)
580                   lookIn.currentRow = row;
581             }
582             if(c == 2)
583                lookIn.currentRow = row;
584          }
585       }
586    #endif
587    }
588
589    // flag = false : Get Name From Edit Box, flag = true : Get Name From List Box
590    void GetNameFromListBox(bool flag)
591    {
592       bool okDisabled;
593       if(flag)
594       {
595          if(style == multiOpen)
596             okDisabled = listBox.numSelections == 0;
597          else
598             okDisabled = listBox.currentRow == null;
599          if(okDisabled) flag = false;
600       }
601       else
602          okDisabled = !fileName || !fileName.line.text[0];
603
604       getNameFromListBox = flag;
605       ok.disabled = okDisabled;
606       open.disabled = okDisabled;
607
608       if(style == selectDir)
609       {
610          if(okDisabled)
611          {
612             ok.disabled = false;
613             ok.caption = $"Select Here";
614             ok.isDefault = true;
615             open.isDefault = false;
616
617             // Never use contents of editbox for 'Select Here'
618             getNameFromListBox = true;
619          }
620          else
621          {
622             ok.caption = $"Select";
623             ok.isDefault = false;
624             open.isDefault = true;
625          }
626       }
627    }
628
629    void ListFiles()
630    {
631       FileListing listing { currentDirectory };
632       int c = 0;
633       FileName fileName;
634       DataRow row;
635
636       if(sizeFilters < sizeof(FileFilter) && !fileFilter)
637          listing.extensions = null;
638       else if(filters && fileFilter < sizeFilters / (int)sizeof(FileFilter))
639          listing.extensions = filters[fileFilter].extensions;
640       else
641          listing.extensions = customFilter.extensions;
642
643       listBox.Clear();
644
645       fileName.indent = 0;
646       while(listing.Find())
647       {
648          if(style != selectDir || (listing.stats.attribs.isDirectory))
649          {
650             row = listBox.AddRow();
651
652             fileName.name = listing.name;
653             if(listing.stats.attribs.isDirectory)
654             {
655                fileName.type = (listing.stats.attribs.isDrive) ? drive : folder;
656                if(listing.stats.attribs.isServer) fileName.type = server;
657                if(listing.stats.attribs.isShare) fileName.type = share;
658                if(listing.stats.attribs.isCDROM) fileName.type = cdrom;
659                if(listing.stats.attribs.isRemote) fileName.type = netDrive;
660                if(listing.stats.attribs.isRemovable)
661                {
662                   if(listing.name[0] == 'A' || listing.name[0] == 'B')
663                      fileName.type = floppy;
664                   else
665                      fileName.type = removable;
666                }
667                row.SetData(nameField, fileName);
668                row.SetData(typeField, null);
669             }
670             else
671             {
672                char extension[MAX_EXTENSION];
673
674                GetExtension(fileName.name, extension);
675                fileName.type = FileNameType::SelectByExtension(extension);
676                strupr(extension);
677                row.SetData(nameField, fileName);
678                row.SetData(typeField, extension);
679                row.SetData(sizeField, listing.stats.size);
680             }
681          }
682          c++;
683       }
684
685       if(sortOrder && sortField != nameField)
686          listBox.Sort(nameField, 1);
687       listBox.Sort(sortField, sortOrder);
688
689       GetNameFromListBox(activeChild == listBox);
690
691       // Go up button
692       if(!strcmp(currentDirectory, "/"))
693       {
694    #ifdef __WIN32__
695          row = listBox.AddRow();
696          fileName.name = msNetwork;
697          fileName.type = network;
698          fileName.indent = 0;
699          row.SetData(nameField, fileName);
700          row.SetData(typeField, null);
701    #endif
702          goUp.disabled = true;
703       }
704       else
705          goUp.disabled = false;
706
707       ListDrives();
708
709       listBox.typingTimeout = 0.5;
710    }
711
712
713    bool GetNamesFromRow(DataRow row, char ** selectedFileName)
714    {
715       FileName * fileName;
716
717       if(style == multiOpen)
718       {
719          OldList selection;
720          listBox.GetMultiSelection(selection);
721          if(selection.count)
722          {
723             int totalLen = 0;
724             OldLink item;
725
726             // First allocate enough memory
727             for(item = selection.first; item; item = item.next)
728             {
729                row = item.data;
730                fileName = row.GetData(nameField);
731    #ifdef __WIN32__
732                if(!strcmp(currentDirectory, "/"))
733                {
734                   if(!strcmp(fileName->name, msNetwork))
735                      totalLen += 2;
736                   else
737                      totalLen += 3;
738                }
739                else
740    #endif
741                totalLen += strlen(fileName->name);
742                if(item != selection.first)
743                   totalLen += 3;
744             }
745             if(selection.count > 1) totalLen += 2;
746             *selectedFileName = new char[totalLen + 1];
747             (*selectedFileName)[0] = '\0';
748
749             // Then build the string
750             for(item = selection.first; item; item = item.next)
751             {
752                row = item.data;
753                fileName = row.GetData(nameField);
754                if(item != selection.first) strcat(*selectedFileName, " ");
755                if(selection.count > 1) strcat(*selectedFileName, "\"");
756    #ifdef __WIN32__
757                if(!strcmp(currentDirectory, "/"))
758                {
759                   char name[3];
760                   if(!strcmp(fileName->name, msNetwork))
761                      strcpy(name, "\\\\");
762                   else
763                   {
764                      name[0] = fileName->name[0];
765                      name[1] = fileName->name[1];
766                      name[2] = '\0';
767                   }
768                   strcat(*selectedFileName, name);
769                }
770                else
771    #endif
772                strcat(*selectedFileName, fileName->name);
773                if(selection.count > 1) strcat(*selectedFileName, "\"");
774             }
775             selection.Free(null);
776             return true;
777          }
778       }
779       else if(row)
780       {
781          fileName = row.GetData(nameField);
782          if(fileName != null)
783          {
784    #ifdef __WIN32__
785             if(!strcmp(currentDirectory, "/"))
786             {
787                *selectedFileName = new char[3];
788                if(!strcmp(fileName->name, msNetwork))
789                   strcpy(*selectedFileName, "\\\\");
790                else
791                {
792                   (*selectedFileName)[0] = fileName->name[0];
793                   (*selectedFileName)[1] = fileName->name[1];
794                   (*selectedFileName)[2] = '\0';
795                }
796             }
797             else
798    #endif
799             {
800                *selectedFileName = new char[strlen(fileName->name) + 1];
801                strcpy(*selectedFileName, fileName->name);
802             }
803             return true;
804          }
805       }
806       return false;
807    }
808
809    bool SelectFile(const char * fileName, FileDialogSelectFrom from, bool isOK)
810    {
811       bool result = true;
812       FileAttribs exists = 0;
813       char * wildcardPointer = fileName ? strstr(fileName, "*") : null;
814
815       if(wildcardPointer)
816       {
817          if(style != selectDir)
818          {
819             int numFilters = sizeFilters / sizeof(FileFilter);
820             int numExtensions = 0;
821             char * pointer = wildcardPointer;
822
823             fileFilter = -1;
824
825             // Count the number of extensions in requested filter
826             while(pointer)
827             {
828                if(pointer[1] == '.' && pointer[2])
829                   pointer +=3;
830                else
831                   pointer ++;
832                pointer = strstr(pointer, "*");
833                numExtensions++;
834             }
835
836             // Try to match the extension(s) to an existing filter
837             if(numFilters)
838             {
839                int filter;
840                char extension[MAX_EXTENSION], compared[MAX_EXTENSION];
841                for(filter = 0; filter<numFilters; filter++)
842                {
843                   if(filters[filter].extensions)
844                   {
845                      int numMatched = 0;
846                      int c, d;
847
848                      for(c = 0; filters[filter].extensions[c];)
849                      {
850                         bool matched = false;
851                         int len = 0;
852                         char ch;
853                         for(;(ch = filters[filter].extensions[c]) && !IS_ALUNDER(ch); c++);
854                         for(;(ch = filters[filter].extensions[c]) &&  IS_ALUNDER(ch); c++)
855                            compared[len++] = ch;
856                         compared[len] = '\0';
857
858                         for(d = 0; wildcardPointer[d]; )
859                         {
860                            len = 0;
861                            for(;(ch = wildcardPointer[d]) && !IS_ALUNDER(ch); d++);
862                            for(;(ch = wildcardPointer[d]) && IS_ALUNDER(ch); d++)
863                            {
864                               if(len < MAX_EXTENSION)
865                                  extension[len++] = ch;
866                            }
867                            extension[len] = '\0';
868
869                            if(!strcmpi(extension, compared))
870                            {
871                               matched = true;
872                               break;
873                            }
874                         }
875
876                         if(matched)
877                            numMatched++;
878                         else
879                         {
880                            numMatched = 0;
881                            break;
882                         }
883                      }
884                      if(numMatched == numExtensions)
885                      {
886                         fileFilter = filter;
887                         break;
888                      }
889                   }
890                   else if(!strcmp(wildcardPointer, "*") || strstr(wildcardPointer, "*.*"))
891                   {
892                      fileFilter = filter;
893                      break;
894                   }
895                }
896             }
897             // Only have *.* to check for
898             else if(!strcmp(wildcardPointer, "*") || strstr(wildcardPointer, "*.*"))
899                this.fileFilter = 0;
900
901             // If we can't match it, use the custom extension
902             if(this.fileFilter == -1)
903             {
904                char extension[MAX_EXTENSION];
905                int c;
906                char * name = new char[numExtensions * (4 + MAX_EXTENSION)];
907                char * customExtensions = (char *)customFilter.extensions;
908                delete customExtensions;
909                customFilter.extensions = null;
910
911                if(!strcmp(wildcardPointer, "*") || strstr(wildcardPointer, "*.*"))
912                {
913                   strcpy(name, $"All Files");
914                   customFilter.extensions = null;
915                }
916                else
917                {
918                   customFilter.extensions = new char[numExtensions * (2 + MAX_EXTENSION)];
919                   ((char *)customFilter.extensions)[0] = '\0';
920                   name[0] = '\0';
921
922                   numExtensions = 0;
923                   for(c = 0; wildcardPointer[c]; )
924                   {
925                      int len = 0;
926                      char ch;
927                      for(;(ch = wildcardPointer[c]) && !IS_ALUNDER(ch); c++);
928                      for(;(ch = wildcardPointer[c]) && IS_ALUNDER(ch); c++)
929                      {
930                         if(len < MAX_EXTENSION)
931                            extension[len++] = ch;
932                      }
933                      extension[len] = '\0';
934
935                      if(numExtensions)
936                      {
937                         strcat(name, ", ");
938                         strcat((char *)customFilter.extensions, ", ");
939                      }
940                      strcat(name, "*.");
941                      strcat(name, extension);
942                      if(!extension[0])
943                         strcat((char *)customFilter.extensions, ".");
944                      else
945                         strcat((char *)customFilter.extensions, extension);
946
947                      numExtensions++;
948                   }
949                }
950
951                if(!customFilterRow)
952                {
953                   customFilterRow = filter.AddRow();
954                   customFilterRow.tag = numFilters + 1;
955                }
956
957                customFilterRow.SetData(null, name);
958
959                fileFilter = numFilters + 1;
960
961                delete name;
962             }
963
964             filter.currentRow = filter.FindRow(fileFilter);
965             ListFiles();
966             result = true;
967          }
968          *wildcardPointer = '\0';
969       }
970
971       if(style == multiOpen)
972       {
973          int c;
974          bool quoted = false;
975          bool needQuotes = strchr(fileName, '\"') != null;
976          char currentFileName[MAX_LOCATION], * curFileName = currentFileName;
977          char ch;
978          OldList selections { };
979          OldLink selection = null;
980
981          for(c = 0;; c++)
982          {
983             ch = fileName[c];
984             switch(ch)
985             {
986                case '\0':
987                case '\"':
988                   if(!ch || quoted)
989                   {
990                      // Add filename
991                      (*curFileName) = '\0';
992
993                      selection = OldLink { data = new char[strlen(currentFileName)+1] };
994                      strcpy(selection.data, currentFileName);
995                      selections.Add(selection);
996
997                      curFileName = currentFileName;
998                   }
999                   quoted ^= true;
1000                   break;
1001                default:
1002                   if(needQuotes && !quoted)
1003                      break;
1004                   // Add to filename
1005                   *(curFileName++) = ch;
1006             }
1007             if(!ch) break;
1008          }
1009
1010          numSelections = 0;
1011          multiFilePaths = new char *[selections.count];
1012
1013          for(selection = selections.first; selection; selection = selection.next)
1014          {
1015             char * fileName = selection.data;
1016
1017             // For every file
1018             strcpy(currentFileName, currentDirectory);
1019             if(PathCat(currentFileName, fileName))
1020             {
1021                FileFixCase(currentFileName);
1022                exists = FileExists(currentFileName);
1023                if(exists.isDirectory)
1024                {
1025                   strcpy(currentDirectory, currentFileName);
1026                   ListFiles();
1027                   break;
1028                }
1029                else if(exists || mayNotExist)
1030                {
1031                   char ** path = &multiFilePaths[numSelections++];
1032                   *path = new char[strlen(currentFileName)+1];
1033                   strcpy(*path, currentFileName);
1034
1035                   result = false;
1036                }
1037                if(exists && result && from == fromListBox) // From List Box
1038                {
1039                   char pathName[MAX_LOCATION];
1040                   GetLastDirectory(this.fileName.line.text, pathName);
1041                   this.fileName.Clear();
1042                   if(!exists.isDirectory)
1043                      this.fileName.PutS(pathName);
1044                   break;
1045                }
1046             }
1047          }
1048          selections.Free(OldLink::Free);
1049          if(result)
1050          {
1051             if(multiFilePaths)
1052             {
1053                for(c = 0; c<numSelections; c++)
1054                   if(multiFilePaths[c])
1055                      delete multiFilePaths[c];
1056                delete multiFilePaths;
1057             }
1058          }
1059       }
1060       else
1061       {
1062          strcpy(filePath, currentDirectory);
1063          if((style == selectDir && !fileName) || PathCat(filePath, fileName) || style == selectDir)
1064          {
1065             FileFixCase(filePath);
1066             exists = FileExists(filePath);
1067             if(exists.isDirectory && (style != selectDir || !isOK))
1068             {
1069                strcpy(currentDirectory, filePath);
1070
1071                ListFiles();
1072             }
1073             else
1074             {
1075                // *** SAVING ONLY ****
1076                if(style == save)
1077                {
1078                   if(fileType >= 0 && fileType < sizeTypes)
1079                   {
1080                      FileType type = types[fileType];
1081                      if(type.forceExtension && type.typeExtension)
1082                      {
1083                         char extension[MAX_EXTENSION];
1084                         GetExtension(filePath, extension);
1085                         if(type.forceExtension == always || !extension[0])
1086                         {
1087                            ChangeExtension(filePath, type.typeExtension, filePath);
1088                            exists = FileExists(filePath);
1089                         }
1090                      }
1091                   }
1092                   if(!exists || MessageBox { master = this, type = yesNo, text = $"File Already Exists", contents = $"Replace existing file?" }.Modal() == yes)
1093                      result = false;
1094                }
1095                else if(exists || mayNotExist)
1096                {
1097                   if(style != selectDir ||
1098                      (strcmp(filePath, "\\\\") &&
1099                      strcmp(filePath, "\\\\") &&
1100                      !(exists.isServer)))
1101                         result = false;
1102                }
1103                // *** DIRECTORY SELECTION ONLY ****
1104                else if(isOK && style == selectDir &&
1105                   MessageBox { this, type = yesNo, text = $"Directory doesn't exist", contents = $"Create directory?" }.Modal() == yes)
1106                {
1107                   if(MakeDir(filePath))
1108                      result = false;
1109                }
1110             }
1111
1112             if(exists && result && from == fromListBox) // From List Box
1113             {
1114                char pathName[MAX_LOCATION];
1115                GetLastDirectory(this.fileName.line.text, pathName);
1116                this.fileName.Clear();
1117                if(!exists.isDirectory)
1118                   this.fileName.PutS(pathName);
1119             }
1120          }
1121       }
1122
1123       if(result && !exists && from == fromDropBox) // drive Drop Box
1124       {
1125          ListDrives();
1126       }
1127
1128       if(!result)
1129          Destroy(DialogResult::ok);
1130       return result;
1131    }
1132
1133    bool OnPostCreate()
1134    {
1135       if(multiFilePaths)
1136       {
1137          int c;
1138          for(c = 0; c<numSelections; c++)
1139             delete multiFilePaths[c];
1140          delete multiFilePaths;
1141       }
1142       numSelections = 0;
1143
1144       ListFiles();
1145       {
1146          // Fix up config input directory
1147          char fileName[MAX_FILENAME];
1148          #if defined(__WIN32__)
1149          char * dirOccur = SearchString(filePath, 0, currentDirectory, false, false);
1150          #else
1151          char * dirOccur = strstr(filePath, currentDirectory);
1152          #endif
1153          if(dirOccur)
1154          {
1155             dirOccur += strlen(currentDirectory);
1156             for(;*dirOccur && (*dirOccur == '/' || *dirOccur == '\\'); dirOccur++);
1157             strcpy(fileName, dirOccur);
1158          }
1159          else
1160             strcpy(fileName, filePath);
1161
1162          this.fileName.Clear();
1163          this.fileName.PutS(fileName);
1164       }
1165
1166       GetNameFromListBox(false);
1167
1168       fileName.MakeActive();
1169       return true;
1170    }
1171
1172    FileDialogType style;
1173    FileFilter customFilter;
1174    DataRow customFilterRow;
1175    bool getNameFromListBox;
1176    BitmapResource icons[FileNameType];
1177
1178    char currentDirectory[MAX_DIRECTORY];
1179    char filePath[MAX_LOCATION];
1180    FileFilter * filters;
1181    int sizeFilters;
1182    FileType * types;
1183    int sizeTypes;
1184    int fileFilter, fileType;
1185
1186    int numSelections;
1187    char ** multiFilePaths;
1188    bool mayNotExist;
1189
1190    // ListBox Configuration
1191    DataField sortField;
1192    int sortOrder;
1193
1194    // File Extension Filter
1195    DropBox filter
1196    {
1197       this, text = $"Filter:", anchor = { left = 96, right = 104, bottom = 16 }, hotKey = altR;
1198
1199       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
1200       {
1201          fileFilter = (int)(row ? row.tag : 0);
1202          ListFiles();
1203          return true;
1204       }
1205    };
1206
1207    Label filterLabel
1208    {
1209       this, inactive = true, anchor = { left = 8, bottom = 3 + 16 }, labeledWindow = filter
1210    };
1211
1212    // File Types
1213    DropBox type
1214    {
1215       this, text = $"As Type:", visible = false, anchor = { left = 96, right = 104, bottom = 16 }, hotKey = altT;
1216
1217       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
1218       {
1219          char * selectedFileName = null;
1220          fileType = (int)(row ? row.tag : 0);
1221          if(style == save && types && types[fileType].typeExtension)
1222          {
1223             if(getNameFromListBox)
1224             {
1225                if(GetNamesFromRow(listBox.currentRow, &selectedFileName))
1226                   fileName.Clear();
1227             }
1228             else
1229             {
1230                const char * fileName = this.fileName.line.text;
1231                //selectedFileName = new char[strlen(fileName)+1];      // Room to change extension???
1232                selectedFileName = new char[MAX_FILENAME];
1233                strcpy(selectedFileName, fileName);
1234             }
1235             ChangeExtension(selectedFileName, types[fileType].typeExtension, selectedFileName);
1236             fileName.Select(null,0,0, null,0,0);
1237             fileName.PutS(selectedFileName);
1238             GetNameFromListBox(false);
1239          }
1240          delete selectedFileName;
1241          return true;
1242       }
1243    };
1244
1245    Label typeLabel
1246    {
1247       this, inactive = true, visible = false, anchor = { left = 8, bottom = 19 }, labeledWindow = type;
1248    };
1249
1250    // Ok Button
1251    Button ok
1252    {
1253       this, isDefault = true, text = $"OK", anchor = { right = 10, bottom = 32 + 16 - 1 }, size = { 80 };
1254
1255       bool NotifyClicked(Button control, int x, int y, Modifiers mods)
1256       {
1257          bool result;
1258          char * selectedFileName = null;
1259          if(getNameFromListBox)
1260          {
1261             GetNamesFromRow(listBox.currentRow, &selectedFileName);
1262             result = SelectFile(selectedFileName, fromListBox, control.id == DialogResult::ok);
1263             if(result && style == selectDir)
1264                listBox.MakeActive();
1265          }
1266          else
1267          {
1268             const char * fileName = this.fileName.line.text;
1269             selectedFileName = new char[strlen(fileName)+1];
1270             strcpy(selectedFileName, fileName);
1271             result = SelectFile(selectedFileName, fromEditBox, control.id == DialogResult::ok);
1272             if(result && style == selectDir)
1273                this.fileName.MakeActive();
1274          }
1275          delete selectedFileName;
1276          return result;
1277       }
1278    };
1279
1280    // Open Button (SelectDir only)
1281    Button open
1282    {
1283       this, visible = false, text = $"Open", hotKey = altO, anchor = { right = 100, bottom = 16 - 1 }, size = { 80 };
1284       NotifyClicked = ok.NotifyClicked;
1285    };
1286
1287    // Cancel Button
1288    Button cancel
1289    {
1290       this, text = $"Cancel", anchor = { right = 10, bottom = 16 - 1 }, size = { 80 }, hotKey = escape;
1291       bool NotifyClicked(Button control, int x, int y, Modifiers mods)
1292       {
1293          Destroy(DialogResult::cancel);
1294          return true;
1295       }
1296    };
1297
1298    // Look In Dropbox
1299    DropBox lookIn
1300    {
1301       this, text = $"Look in:", anchor = { left = 81, top = 8, right = 109 }, hotKey = altL, maxShown = 12;
1302
1303       bool OnKeyHit(Key key, unichar ch)
1304       {
1305          if(key == wheelDown || key == wheelUp)
1306          {
1307             ((FileDialog)master).listBox.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit, key, ch);
1308             return false;
1309          }
1310          return true;
1311       }
1312
1313       bool NotifySelect(DropBox control, DataRow row, Modifiers mods)
1314       {
1315          if(row)
1316          {
1317             FileName * fileName = row.GetData(null);
1318             char name[MAX_LOCATION] = "/";
1319             int indent = 0;
1320             int c = 0;
1321          #ifdef __WIN32__
1322             if(!strcmp(fileName->name, msNetwork) ||
1323                (currentDirectory[0] == '\\' &&
1324                 currentDirectory[1] == '\\' &&
1325                 strcmp(fileName->name, rootName) &&
1326                 (!fileName->name[0] || fileName->name[1] != ':')))
1327             {
1328                strcpy(name, "\\\\");
1329                c = 2;
1330             }
1331             else
1332             {
1333                if(fileName->indent > 1)
1334                {
1335                   name[0] = currentDirectory[0];
1336                   name[1] = ':';
1337                   name[2] = '\0';
1338                   indent++;
1339                   c = 2;
1340                }
1341                else if(fileName->indent == 1)
1342                {
1343                   name[0] = fileName->name[0];
1344                   name[1] = ':';
1345                   name[2] = '\0';
1346                   c = 2;
1347                   indent++;
1348                }
1349             }
1350          #endif
1351             if(indent < fileName->indent)
1352             {
1353                for(; currentDirectory[c]; )
1354                {
1355                   int len = 0;
1356                   char ch;
1357                   char directory[MAX_FILENAME];
1358                   for(;(ch = currentDirectory[c]) && (ch == '/' || ch == '\\'); c++);
1359                   for(;(ch = currentDirectory[c]) && (ch != '/' && ch != '\\'); c++)
1360                   {
1361                      if(len < MAX_FILENAME)
1362                         directory[len++] = ch;
1363                   }
1364                   for(;(ch = currentDirectory[c]) && (ch == '/' || ch == '\\'); c++);
1365                   directory[len] = '\0';
1366
1367                   if(indent >= fileName->indent) break;
1368                   PathCat(name, directory);
1369                   indent++;
1370                }
1371             }
1372
1373             SelectFile(name, fromDropBox, false);
1374          }
1375          return true;
1376       }
1377    };
1378
1379    DataField lookInField { dataType = "FileName", userData = this };
1380
1381    Label lookInLabel
1382    {
1383       this, position = { 10, 11 }, labeledWindow = lookIn;
1384    };
1385
1386    // Main Listbox
1387    ListBox listBox
1388    {
1389       this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true,
1390       anchor = { left = 8, right = 8, top = 40, bottom = 64 + 16 },
1391       hasHeader = true, moveFields = true, resizable = true, sortable = true;
1392
1393       bool NotifySelect(ListBox control, DataRow row, Modifiers mods)
1394       {
1395          GetNameFromListBox(true);
1396          return true;
1397       }
1398
1399       bool NotifyDoubleClick(ListBox control, int x, int y, Modifiers mods)
1400       {
1401          bool result = true;
1402          char * selectedFileName = null;
1403          if(GetNamesFromRow(control.currentRow, &selectedFileName))
1404          {
1405             result = SelectFile(selectedFileName, fromEditBox, false);
1406          }
1407          delete selectedFileName;
1408          return result;
1409       }
1410
1411       bool NotifyKeyDown(ListBox control, DataRow row, Key key, unichar ch)
1412       {
1413          if(key == backSpace)
1414          {
1415             if(strcmp(currentDirectory, "/"))
1416                goUp.NotifyClicked(this, goUp, 0,0, key.modifiers);
1417          }
1418          return true;
1419       }
1420
1421       bool NotifySort(ListBox control, DataField field, Modifiers mods)
1422       {
1423          sortField = field;
1424          sortOrder = field.sortOrder;
1425          return true;
1426       }
1427
1428       bool NotifyActivate(Window control, bool active, Window previous)
1429       {
1430          if(active)
1431             GetNameFromListBox(true);
1432          return true;
1433       }
1434    };
1435
1436    DataField nameField { header = $"Name", dataType = "FileName", width = 304, userData = this }; // editable = true
1437    DataField typeField { header = $"Type", dataType = /*"String"*/ "char *", width = 40 };
1438    DataField sizeField { header = $"Size", dataType = "FileSize", width = 96, alignment = right };
1439
1440    // Go up button
1441    Button goUp
1442    {
1443       this, inactive = true, anchor = { right = 79, top = 8 }, size = { 24, 24 },
1444       bitmap = { "<:ecere>actions/goUp.png", alphaBlend = true };
1445       symbol = 30;
1446
1447       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1448       {
1449          char * notRoot;
1450          notRoot = StripLastDirectory(currentDirectory, currentDirectory);
1451          if(!notRoot)
1452             strcpy(currentDirectory, "/");
1453          ListFiles();
1454          return true;
1455       }
1456    };
1457
1458    Button createDirectory
1459    {
1460       this, inactive = true, anchor = { right = 51, top = 8 }, size = { 24, 24 },
1461       bitmap = { "<:ecere>actions/folderNew.png", alphaBlend = true };
1462       symbol = 30;    // what the heck is that?
1463
1464       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1465       {
1466          if(CreateDirectoryDialog { master = this, parent = parent, currentDirectory = currentDirectory }.Modal() == ok )
1467             ListFiles();
1468          return true;
1469       }
1470    };
1471
1472    // File name editbox
1473    EditBox fileName
1474    {
1475       this, text = $"File Name:", anchor = { left = 96, bottom = 32 + 16, right = 104 }, size.h = 20, hotKey = altF;
1476
1477       bool NotifyActivate(Window control, bool active, Window previous)
1478       {
1479          if(active)
1480          {
1481             char * selectedFileName = null;
1482             const char * editText;
1483             if(getNameFromListBox)
1484             {
1485                DataRow row = listBox.currentRow;
1486                if(row)
1487                {
1488                   if(GetNamesFromRow(row, &selectedFileName))
1489                   {
1490                      fileName.Clear();
1491                      fileName.PutS(selectedFileName);
1492                      fileName.Select(null,0,0, null,0,0);
1493                   }
1494                   GetNameFromListBox(false);
1495                }
1496             }
1497             delete selectedFileName;
1498             editText = fileName.contents;
1499             if(style != selectDir)
1500                ok.disabled = !editText || !editText[0];
1501          }
1502          return true;
1503       }
1504
1505       void NotifyUpdate(EditBox control)
1506       {
1507          GetNameFromListBox(false);
1508       }
1509    };
1510
1511    Label fileNameLabel
1512    {
1513       this, inactive = true, anchor = { left = 8, bottom = 35 + 16 };
1514       labeledWindow = fileName;
1515    };
1516 };
1517
1518 public class CreateDirectoryDialog : Window
1519 {
1520    background = formColor;
1521    minClientSize = Size { 240, 100 };
1522    tabCycle = true;
1523    hasClose = true;
1524    text = $"Create Directory";
1525
1526 public:
1527
1528    property const char * currentDirectory
1529    {
1530       set
1531       {
1532          GetWorkingDir(currentDirectory, MAX_DIRECTORY);  // is this necessary?
1533          PathCat(currentDirectory, value);
1534          FileFixCase(currentDirectory);
1535       }
1536       get { return (char *)currentDirectory; }
1537    };
1538
1539 private:
1540
1541    char currentDirectory[MAX_DIRECTORY];
1542
1543    CreateDirectoryDialog()
1544    {
1545       GetWorkingDir(currentDirectory, MAX_DIRECTORY);
1546       FileFixCase(currentDirectory);
1547    }
1548
1549    ~CreateDirectoryDialog()
1550    {
1551    }
1552
1553    bool OnPostCreate()
1554    {
1555       newDirectoryName.SelectAll();
1556       return true;
1557    }
1558
1559    Button ok
1560    {
1561       parent = this, isDefault = true, position = { 70, 60 }, size = { 60 }, text = $"OK";
1562       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1563       {
1564          if(newDirectoryName.contents && newDirectoryName.contents[0])
1565          {
1566             char newDirPath[MAX_DIRECTORY];
1567             strcpy(newDirPath, currentDirectory);
1568             PathCat(newDirPath, newDirectoryName.contents);
1569             if(!FileExists(newDirPath).isDirectory)
1570             {
1571                MakeDir(newDirPath);
1572                Destroy(DialogResult::ok);
1573             }
1574             else
1575                MessageBox { master = this, parent = parent, type = ok, text = $"Create Directory Error", contents = $"Directory already exists." }.Modal();
1576          }
1577          else
1578             MessageBox { master = this, parent = parent, type = ok, text = $"Create Directory Error", contents = $"Please enter a name." }.Modal();
1579          return true;
1580       }
1581    };
1582
1583    Button cancel
1584    {
1585       parent = this, position = { 140, 60 }, size = { 60 }, hotKey = escape, text = $"Cancel";
1586       NotifyClicked = ButtonCloseDialog;
1587    };
1588
1589    EditBox newDirectoryName
1590    {
1591       this, textHorzScroll = true, anchor = { left = 10, right = 10, top = 30 }, size = { 250 };
1592       hotKey = altN, text = $"Name";
1593       contents = $"New Directory";
1594    };
1595    Label { this, position = { 10, 10 }, labeledWindow = newDirectoryName };
1596
1597 }