ide/extras/PathBox: Fixed crash (http://ecere.com/mantis/view.php?id=600) in NewProje...
[sdk] / extras / gui / controls / PathBox.ec
1 #ifdef BUILDING_ECERE_COM
2 namespace gui::controls;
3 import "Window"
4 #else
5 #ifdef ECERE_STATIC
6 public import static "ecere"
7 #else
8 public import "ecere"
9 #endif
10 #endif
11
12 // We have this dependency because of important 'browsing' to keep track
13 import "DirectoriesBox"
14
15 void MakeSlashPath(char * p)
16 {
17    FileFixCase(p);
18 #ifdef WIN32
19    ChangeCh(p, '\\', '/');
20 #endif
21 }
22
23 void MakeSystemPath(char * p)
24 {
25    /*
26 #if defined(__WIN32__)
27    ChangeCh(p, '/', '\\');
28 #else
29    ChangeCh(p, '\\', '/');
30 #endif*/
31    FileFixCase(p);
32 }
33
34 public char * CopySystemPath(char * p)
35 {
36    char * d = CopyString(p);
37    if(d)
38       MakeSystemPath(d);
39    return d;
40 }
41
42 public char * CopyUnixPath(char * p)
43 {
44    char * d = CopyString(p);
45    if(d)
46       MakeSlashPath(d);
47    return d;
48 }
49
50 public char * GetSystemPathBuffer(char * d, char * p)
51 {
52    if(d != p)
53       strcpy(d, p ? p : "");
54    MakeSystemPath(d);
55    return d;
56 }
57
58 public char * GetSlashPathBuffer(char * d, char * p)
59 {
60    if(d != p)
61       strcpy(d, p ? p : "");
62    MakeSlashPath(d);
63    return d;
64 }
65
66 public char * PathCatSlash(char * string, char * addedPath)
67 {
68    bool modified = false;
69    if(addedPath)
70    {
71       char fileName[MAX_LOCATION] = "", archiveName[MAX_LOCATION] = "", * file;
72       int c = 0;
73       bool isURL = false;
74       char * urlFileName;
75
76       if(SplitArchivePath(string, archiveName, &file))
77          strcpy(fileName, file);
78       else
79       {
80          strcpy(fileName, string);
81       }
82
83       if(strstr(string, "http://") == string)
84       {
85          char * slash = strstr(fileName + 7, "/");
86          isURL = true;
87          if(slash)
88             urlFileName = slash;
89          else
90             urlFileName = fileName + strlen(fileName);
91       }
92       if(strstr(addedPath, "http://") == addedPath)
93       {
94          strcpy(fileName, "http://");
95          isURL = true;
96          c = 7;
97       }
98       else if(GetRuntimePlatform() == win32)
99       {
100          if(addedPath[0] && addedPath[1] == ':' && addedPath[0] != '<')
101          {
102             fileName[0] = (char)toupper(addedPath[0]);
103             fileName[1] = ':';
104             fileName[2] = '\0';
105             c = 2;
106             modified = true;
107          }
108          else if(addedPath[0] == '\\' && addedPath[1] == '\\')
109          {
110             fileName[0] = fileName[1] = '\\';
111             fileName[2] = '\0';
112             c = 2;
113             modified = true;
114          }
115          // A drive needs to be selected
116          /* TOCHECK: Cutting this out, can't handle relative path
117          else if(fileName[0] == '/' && !archiveName[0] && strcmp(addedPath, "/"))
118             return null;
119          */
120       }
121
122       if(!modified && isURL && (addedPath[0] == '\\' || addedPath[0] == '/'))
123       {
124          urlFileName[0] = '/';
125          urlFileName[1] = '\0';
126       }
127       else if(!modified && (addedPath[0] == '\\' || addedPath[0] == '/'))
128       {
129          if(GetRuntimePlatform() == win32)
130          {
131             // Entire Computer
132             if(addedPath[0] == '/' && !addedPath[1])
133             {
134                fileName[0] = addedPath[0];
135                fileName[1] = '\0';
136                modified = true;
137             }
138             // Root of drive
139             else if(fileName[0] && fileName[1] == ':')
140             {
141                fileName[2] = '\0';
142                modified = true;
143             }
144             // Relative path root of drive
145             else
146             {
147                fileName[0] = '\\';
148                fileName[1] = '\0';
149                modified = true;
150             }
151          }
152          else
153          {
154             fileName[0] = '/';
155             fileName[1] = '\0';
156             modified = true;
157          }
158          c = 1;
159       }
160
161       for(; addedPath[c]; )
162       {
163          // DANGER OF OVERFLOW HERE
164          // char directory[MAX_FILENAME];
165          char directory[MAX_FILENAME * 16];
166          int len = 0;
167          char ch;
168          int count;
169       
170          for(;(ch = addedPath[c]) && (ch == '/' || ch == '\\'); c++);
171          for(;(ch = addedPath[c]) && (ch != '/' && ch != '\\'); c++)
172          {
173             if(isURL && ch == '?')
174             {
175                break;
176             }
177             if(len < MAX_FILENAME)
178                directory[len++] = ch;  
179          }
180          directory[len] = '\0';
181
182          // Trim rightmost spaces
183          for(count = len-1; count >= 0 && (directory[count] == ' ' || directory[count] == '\t'); count--)
184          {
185             directory[count] = '\0';
186             len--;
187          }
188
189          if(len > 0)
190          {
191             modified = true;
192             if(strstr(directory, "..") == directory && (!directory[2] || directory[2] == DIR_SEP || directory[2] == '/'))
193             {
194                int strLen = strlen(fileName) - 1;
195                if(strLen > -1)
196                {
197                   // Go back one directory
198                   for(;(ch = fileName[strLen]) && strLen > -1 && (ch == '/' || ch == '\\'); strLen--);
199                   for(;(ch = fileName[strLen]) && strLen > -1 && (ch != '/' && ch != '\\' && ch != ':'); strLen--);
200                   for(;(ch = fileName[strLen]) && strLen > -1 && (ch == '/' || ch == '\\'); strLen--);
201
202                   if(isURL)
203                   {
204                      strLen = Max(strLen, urlFileName - fileName);
205                   }
206                   if(!strcmp(fileName + strLen + 1, ".."))
207                   {
208                      strcat(fileName, "/" /*DIR_SEPS*/);
209                      strcat(fileName, "..");
210                   }
211                   else
212                   {
213                      if(GetRuntimePlatform() == win32)
214                      {
215                         if(!strLen && fileName[0] == '\\' && fileName[1] == '\\')
216                         {
217                            if(!fileName[2])
218                               return null;
219                            else
220                            {
221                               fileName[0] = '\\';
222                               fileName[1] = '\\';
223                               fileName[2] = '\0';
224                            }
225                         }
226                         else
227                            fileName[strLen+1] = '\0';
228                      }
229                      else
230                      {
231                         fileName[strLen+1] = '\0';
232                         if(strLen<0)
233                         {
234                            fileName[0] = '/';
235                            fileName[1] = '\0';
236                            strLen = 2;
237                         }
238                      }
239                   }
240                }
241                else
242                {
243                   strcpy(fileName, "..");
244                }
245             }
246             else if(strcmp(directory, "."))
247             {
248                int strLen = strlen(fileName);
249                bool notZeroLen = strLen > 0;
250                // if(strLen > 1 && (fileName[strLen-1] == '/' || fileName[strLen-1] == '\\'))
251                if(strLen > 0 && (fileName[strLen-1] == '/' || fileName[strLen-1] == '\\'))
252                   strLen--;
253                if(notZeroLen /*&& fileName[strLen-1] != ':' && fileName[strLen-1] != '>'*/)
254                   fileName[strLen++] = '/';
255
256                fileName[strLen] = '\0';
257
258                if(strLen + strlen(directory) > MAX_LOCATION - 3)
259                   return null;   // AN ERROR OCCURED!
260
261                strcat(fileName, directory);
262             }
263          }
264          if(isURL && ch == '/')
265             strcat(fileName, "/");
266          if(isURL && ch == '?')
267          {
268             strcat(fileName, addedPath+c);
269             break;
270          }
271       }
272       if(archiveName[0])
273          sprintf(string, "<%s>%s", archiveName, fileName);
274       else
275          strcpy(string, fileName);
276    }
277    return modified ? string : null;
278 }
279
280 default extern int __ecereVMethodID_class_OnGetDataFromString;
281 default static void _workAround()
282 {
283    int a;
284    a.OnGetDataFromString(0);
285 }
286
287 public class DirPath : String
288 {
289    Window OnEdit(DataBox dataBox, DataBox obsolete, int x, int y, int w, int h, void * userData)
290    {
291       PathBox pathBox
292       {
293          dataBox, borderStyle = 0, anchor = { 0, 0, 0, 0 },
294          browseDialog = (FileDialog)userData;
295          visible = false;
296
297          void OnPathBrowsed(char * browsedPath)
298          {
299             DataBox dataBox = (DataBox)parent;
300             ListBox lb = eClass_IsDerived(dataBox.parent._class, class(ListBox)) ? (ListBox)dataBox.parent : null;
301             property::path = browsedPath;
302             if(lb) lb.StopEditing(true);
303          }
304       };
305       pathBox.path = this;
306       pathBox.visible = true;
307       pathBox.Create();
308       if(!dataBox.active)
309       {
310          pathBox.editBox.Deselect();
311          pathBox.editBox.Home();
312       }
313       return pathBox;
314    }
315
316    bool OnSaveEdit(PathBox pathBox, void * object)
317    {
318       bool changed = false;
319       if(pathBox.modifiedDocument)
320       {
321          /*
322          char path[MAX_LOCATION];
323          char * c = pathBox.path;
324          GetSystemPathBuffer(path, c);
325          if(strcmp(path, c))
326             pathBox.editBox.contents = path;
327          */
328
329          String::OnFree();
330          // changed = String::OnGetDataFromString(path);  // TODO: Fix Me
331          changed = _class._vTbl[__ecereVMethodID_class_OnGetDataFromString](_class, &this, pathBox.systemPath);
332       }
333       return changed;
334    }
335
336    int OnCompare(DirPath b)
337    {
338       return fstrcmp(this, b);
339    }
340
341 }
342
343 public enum PathTypeExpected { none, any, directory, file };
344
345 public class PathBox : CommonControl
346 {
347    borderStyle = deep;
348    clientSize = { 64, 18 };
349
350 #if defined(__WIN32__)
351    PathBox()
352    {
353       path[0] = '\0';
354    }
355 #endif
356
357    PathTypeExpected typeExpected;
358    FileDialog browseDialog;
359 #if defined(__WIN32__)
360    char path[MAX_LOCATION];
361 #endif
362
363    BitmapResource file { "<:ecere>mimeTypes/file.png", transparent = true, alphaBlend = true };
364    BitmapResource brokenFile { "<:ecere>mimeTypes/brokenFile.png", transparent = true, alphaBlend = true };
365    BitmapResource folder { "<:ecere>places/folder.png", transparent = true, alphaBlend = true };
366    BitmapResource brokenFolder { "<:ecere>places/brokenFolder.png", transparent = true, alphaBlend = true };
367
368    Picture picture
369    {
370       this/*, size = { 318, 94 }*/, anchor = { left = 1, vert = 0 };
371       image = brokenFolder;
372       visible = false;
373       opacity = 0;
374       alphaBlend = true;
375    };
376
377    EditBox editBox
378    {
379       this/*, size = { 290, 22 }*/, position = { 1, 1 }, anchor = { left = 1, top = 1, right = 1 };
380       borderStyle = none;
381
382       void NotifyUpdate(EditBox editBox)
383       {
384          CheckFileExists();
385          modifiedDocument = true;
386       }
387       bool NotifyModified(EditBox editBox)
388       {
389          return NotifyModified(master, this);
390       }
391
392       bool OnKeyDown(Key key, unichar ch)
393       {
394          if(key == f4)
395             ((PathBox)parent).browse.NotifyClicked(parent, ((PathBox)parent).browse, 0, 0, (Modifiers)null);
396          return EditBox::OnKeyDown(key, ch);
397       }
398    };
399
400    // For chaining popup-key event
401    bool OnKeyHit(Key key, unichar ch)
402    {
403       return editBox.OnKeyHit(key, ch);
404    }
405
406    Button browse
407    {
408       this, size = { w = 26 }, anchor = { top = 0, right = 0, bottom = 0 };
409       text = "...";
410       hotKey = f2;
411       visible = false;
412       inactive = true;
413
414       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
415       {
416          if(browseDialog)
417          {
418             char * browsePath = CopyString(OnBrowse());
419             char fileName[MAX_LOCATION];//, filePath[MAX_LOCATION];
420             DirectoriesBox dirBox = (DirectoriesBox)parent.parent;
421             if(dirBox) { dirBox = (DirectoriesBox)dirBox.parent; } // TOFIX: Precomp needs { }
422             if(dirBox && !eClass_IsDerived(dirBox._class, class(DirectoriesBox))) dirBox = null;
423
424             incref this;
425
426             GetLastDirectory(browsePath, fileName);
427             StripLastDirectory(browsePath, browsePath);
428
429             if(!browsePath[0])
430             {
431                char filePath[MAX_LOCATION];
432                delete browsePath;
433                LocateModule(null, filePath);
434                StripLastDirectory(filePath, filePath);
435                browsePath = CopyString(filePath);
436             }
437
438             while(browsePath[0] && !FileExists(browsePath).isDirectory)
439             {
440                char temp[MAX_LOCATION];
441                GetLastDirectory(browsePath, temp);
442                PathCat(temp, fileName);
443                strcpy(fileName, temp);
444                StripLastDirectory(browsePath, browsePath);
445             }
446             browseDialog.filePath = fileName;
447             browseDialog.currentDirectory = browsePath;
448             delete browsePath;
449             browseDialog.master = rootWindow;
450
451             // THIS PART WAS MISSING IN THE PathBox INTEGRATION AND WAS CRUCIAL
452             if(dirBox) dirBox.browsing = true;
453             if(browseDialog.Modal())
454             {
455                modifiedDocument = true;
456                OnPathBrowsed(browseDialog.filePath);
457                NotifyModified(master, this);
458             }
459             if(dirBox) dirBox.browsing = false;
460
461             delete this;
462          }
463          return true;
464       }
465    };
466
467    void CheckFileExists()
468    {
469       if(typeExpected != none)
470       {
471          BitmapResource icon = null;
472          FileAttribs exists = FileExists(editBox.contents);
473          
474          //printf("%s\n", editBox.contents);
475          switch(typeExpected)
476          {
477             case any:
478                // TODO: improvements, add drive, etc, also find a better solution/icon for expect any and file doesn't exist
479                icon = exists ? exists.isFile ? file : exists.isDirectory ? folder : null : null;
480                break;
481             case directory:
482                icon = exists && exists.isDirectory ? folder : brokenFolder;
483                break;
484             case file:
485                icon = exists && exists.isFile ? file : brokenFile;
486                break;
487          }
488
489          picture.image = icon;
490       }
491    }
492
493    void OnRedraw(Surface surface)
494    {
495       if(!isEnabled)
496       {
497          surface.SetBackground(activeBorder);
498          surface.Area(0, 0, clientSize.w, clientSize.h);
499       }
500       else
501          Window::OnRedraw(surface);
502    }
503
504    ~PathBox()
505    {
506       delete browseDialog;
507    }
508
509 public:
510    property PathTypeExpected typeExpected
511    {
512       set
513       {
514          if(value != typeExpected)
515          {
516             if(value == none || typeExpected == none)
517             {
518                bool withIcon = value != none;
519                picture.visible = withIcon;
520                editBox.anchor.left = withIcon ? 18 : 1;
521                CheckFileExists();
522             }
523             typeExpected = value;
524             if(browseDialog && browseDialog.type == open && typeExpected == directory)
525                browseDialog.type = selectDir;
526          }         
527       }
528    }
529
530    property FileDialog browseDialog
531    {
532       set
533       {
534          delete browseDialog;
535          browseDialog = value;
536          if(browseDialog)
537          {
538             incref browseDialog;
539             if(browseDialog.type == open && typeExpected == directory)
540                browseDialog.type = selectDir;
541             if(!strcmp(browseDialog.text, "Select a file...") && text)
542             {
543                char temp[1024] = "Select ";
544                strcat(temp, text);
545                strcat(temp, "...");
546                browseDialog.text = temp;
547             }
548          }
549          browse.visible = browseDialog ? true : false;
550          editBox.anchor.right = browseDialog ? 26 : 1;
551       }
552    }
553
554    void Home() { editBox.Home(); }
555    void End() { editBox.End(); }
556
557    void SelectAll() { editBox.SelectAll(); }
558    void Deselect() { editBox.Deselect(); }
559
560    property String path
561    {
562       set
563       {
564          char path[MAX_LOCATION];
565          GetSystemPathBuffer(path, value);
566          editBox.contents = path;
567          if(active)
568             editBox.SelectAll();
569          CheckFileExists();
570       }
571       get { return editBox.contents; }
572    }
573    property String slashPath  { get { return GetSlashPathBuffer (path, editBox.contents); } };
574    property String systemPath { get { return GetSystemPathBuffer(path, editBox.contents); } };
575
576    virtual char * OnBrowse()
577    {
578       return editBox.contents;
579    }
580
581    virtual void OnPathBrowsed(char * browsedPath)
582    {
583       property::path = browsedPath;
584       editBox.SelectAll();
585    }
586
587    virtual bool Window::NotifyModified(PathBox pathBox);
588 }