ide, PathBox; fixed when selecting a directory in global settings, default keeps...
[sdk] / ecere / src / gui / controls / PathBox.ec
1 namespace gui::controls;
2 import "Window"
3 import "Array"
4
5 default extern int __ecereVMethodID_class_OnGetDataFromString;
6 default static void _workAround()
7 {
8    int a;
9    a.OnGetDataFromString(0);
10 }
11
12
13 /* We need a dynamic cast :) Easy eC compiler introduction contribution! :)
14 static define pathBoxDataBox =                        dynamic_cast(pathBox.master);
15 static define pathBoxListBox = pathBoxEditorDataBox ? dynamic_cast((pathBoxDataBox).parent) : null;
16 static define pathBoxDirsBox = pathBoxEditorListBox ? dynamic_cast((pathBoxListBox).parent) : null;
17 */
18 static Class _classDataBox() { return class(DataBox); }
19 static Class _classListBox() { return class(ListBox); }                              // Fix #908
20 static Class _classDirectoriesBox() { return class(DirectoriesBox); }
21 static define pathBoxDataBox =                    eClass_IsDerived(         pathBox.master._class, _classDataBox()        ) ?                 (DataBox)pathBox.master : null;
22 static define pathBoxListBox = (pathBoxDataBox && eClass_IsDerived((pathBoxDataBox).parent._class, _classListBox()       )) ?        (ListBox)(pathBoxDataBox).parent : null;
23 static define pathBoxDirsBox = (pathBoxListBox && eClass_IsDerived((pathBoxListBox).parent._class, _classDirectoriesBox())) ? (DirectoriesBox)(pathBoxListBox).parent : null;
24
25 public class FilePath : String
26 {
27    Window OnEdit(DataBox dataBox, DataBox obsolete, int x, int y, int w, int h, void * userData)
28    {
29       DirectoriesBox dirsBox;
30       PathBox pathBox
31       {
32          dataBox, borderStyle = 0, anchor = { 0, 0, 0, 0 },
33          typeExpected = any;
34          path = this;
35       };
36       if((dirsBox = pathBoxDirsBox))
37          pathBox.browseDialog = dirsBox.browseDialog;
38       // TOCHECK: compiler issues?
39       /*else if(userData && eClass_IsDerived(userData._class, class(FileDialog)))
40          pathBox.browseDialog = (FileDialog)userData;*/
41       else
42          pathBox.browseDialog = { type = open, text = $"Select a file..." };
43       pathBox.Create();
44       return pathBox;
45    }
46
47    bool OnSaveEdit(PathBox pathBox, void * object)
48    {
49       bool changed = false;
50       if(pathBox.modifiedDocument)
51       {
52          DirectoriesBox dirsBox;
53          if((dirsBox = pathBoxDirsBox))
54             dirsBox.NotifyPathBoxModified(dirsBox.master, dirsBox, pathBox);
55          String::OnFree();
56          changed = ((bool (*)(void *, void *, const char *))(void *)_class._vTbl[__ecereVMethodID_class_OnGetDataFromString])(_class, &this, pathBox.systemPath);
57       }
58       return changed;
59    }
60
61    int OnCompare(DirPath b)
62    {
63       return fstrcmp(this, b);
64    }
65 }
66
67 public class DirPath : FilePath
68 {
69    Window OnEdit(DataBox dataBox, DataBox obsolete, int x, int y, int w, int h, void * userData)
70    {
71       DirectoriesBox dirsBox;
72       PathBox pathBox
73       {
74          dataBox, borderStyle = 0, anchor = { 0, 0, 0, 0 },
75          typeExpected = directory;
76          path = this;
77       };
78       if((dirsBox = pathBoxDirsBox))
79          pathBox.browseDialog = dirsBox.browseDialog;
80       // TOCHECK: compiler issues? (same)
81       /*else if(userData && eClass_IsDerived(userData._class, class(FileDialog)))
82          pathBox.browseDialog = (FileDialog)userData;*/
83       else
84          pathBox.browseDialog = { type = selectDir, text = $"Select a folder..." };
85       pathBox.Create();
86       return pathBox;
87    }
88
89    bool OnSaveEdit(PathBox pathBox, void * object)
90    {
91       bool changed = false;
92       if(pathBox.modifiedDocument)
93       {
94          DirectoriesBox dirsBox;
95          if((dirsBox = pathBoxDirsBox))
96             dirsBox.NotifyPathBoxModified(dirsBox.master, dirsBox, pathBox);
97          String::OnFree();
98          changed = ((bool (*)(void *, void *, const char *))(void *)_class._vTbl[__ecereVMethodID_class_OnGetDataFromString])(_class, &this, pathBox.systemPath);
99       }
100       return changed;
101    }
102
103 }
104
105 public enum PathTypeExpected { none, any, directory, file };
106
107 public class PathBox : CommonControl
108 {
109    borderStyle = deep;
110    clientSize = { 64, 18 };
111
112    watch(background) { editBox.background = background; };
113    watch(foreground) { editBox.foreground = foreground; };
114    watch(opacity)    { editBox.opacity    = opacity; };
115
116 #if defined(__WIN32__)
117    PathBox()
118    {
119       path[0] = '\0';
120    }
121 #endif
122
123    PathTypeExpected typeExpected;
124    FileDialog browseDialog;
125 #if defined(__WIN32__)
126    char path[MAX_LOCATION];
127 #endif
128
129    BitmapResource file { "<:ecere>mimeTypes/file.png", transparent = true, alphaBlend = true };
130    BitmapResource brokenFile { "<:ecere>mimeTypes/brokenFile.png", transparent = true, alphaBlend = true };
131    BitmapResource folder { "<:ecere>places/folder.png", transparent = true, alphaBlend = true };
132    BitmapResource brokenFolder { "<:ecere>places/brokenFolder.png", transparent = true, alphaBlend = true };
133
134    Picture picture
135    {
136       this/*, size = { 318, 94 }*/, anchor = { left = 1, vert = 0 };
137       image = brokenFolder;
138       visible = false;
139       opacity = 0;
140       alphaBlend = true;
141    };
142
143    public property EditBox editBox { get { return editBox; } }
144    EditBox editBox
145    {
146       this/*, size = { 290, 22 }*/, position = { 1, 1 }, anchor = { left = 1, top = 1, right = 1 };
147       borderStyle = none;
148
149       void NotifyUpdate(EditBox editBox)
150       {
151          CheckFileExists();
152          modifiedDocument = true;
153       }
154       bool NotifyModified(EditBox editBox)
155       {
156          PathBox pathBox = this;
157          DirectoriesBox dirsBox;
158          if((dirsBox = pathBoxDirsBox))
159             dirsBox.NotifyPathBoxModified(dirsBox.master, dirsBox, this);
160          return NotifyModified(master, this);
161       }
162
163       /* F4? F4 is properties, f2 works already for this
164       bool OnKeyDown(Key key, unichar ch)
165       {
166          if(key == f4)
167             ((PathBox)parent).browse.NotifyClicked(parent, ((PathBox)parent).browse, 0, 0, (Modifiers)null);
168          return EditBox::OnKeyDown(key, ch);
169       }
170       */
171    };
172
173    // For chaining popup-key event
174    bool OnKeyHit(Key key, unichar ch)
175    {
176       return editBox.OnKeyHit(key, ch);
177    }
178
179    Button browse
180    {
181       this, size = { w = 26 }, anchor = { top = 0, right = 0, bottom = 0 };
182       text = "...";
183       hotKey = f2;
184       visible = false;
185       inactive = true;
186
187       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
188       {
189          if(browseDialog)
190          {
191             char * browsePath = new char[MAX_LOCATION];
192             char * fileName = new char[MAX_LOCATION];
193             char * baseBrowsePath = null;
194             PathBox pathBox = this;
195             DataBox dataBox = pathBoxDataBox;
196             ListBox listBox;
197             DirectoriesBox dirsBox;
198             browsePath[0] = fileName[0] = '\0';
199             strncpy(browsePath, browseDialog.filePath, MAX_LOCATION); browsePath[MAX_LOCATION-1] = '\0';
200             if((dirsBox = pathBoxDirsBox) && dirsBox.baseBrowsePath && dirsBox.baseBrowsePath[0])
201                PathCat(browsePath, dirsBox.baseBrowsePath);
202             PathCat(browsePath, editBox.contents);
203             if(browsePath[0])
204             {
205                GetLastDirectory(browsePath, fileName);
206                StripLastDirectory(browsePath, browsePath);
207             }
208             else
209             {
210                char * path = new char[MAX_LOCATION];
211                LocateModule(null, path);
212                StripLastDirectory(path, path);
213                strncpy(browsePath, path, MAX_LOCATION); browsePath[MAX_LOCATION-1] = '\0';
214                delete path;
215             }
216             while(browsePath[0] && !FileExists(browsePath).isDirectory)
217             {
218                char * temp = new char[MAX_LOCATION];
219                GetLastDirectory(browsePath, temp);
220                PathCat(temp, fileName);
221                strcpy(fileName, temp);
222                StripLastDirectory(browsePath, browsePath);
223                delete temp;
224             }
225             browseDialog.filePath = fileName;
226             browseDialog.currentDirectory = browsePath;
227             browseDialog.master = rootWindow;
228             delete browsePath;
229             delete fileName;
230
231             incref this;
232             if(dirsBox) dirsBox.browsing = true;
233             if(browseDialog.Modal())
234             {
235                PathBox pathBox = dataBox ? (PathBox)dataBox.editor : this;
236                pathBox.modifiedDocument = true;
237                pathBox.property::path = browseDialog.filePath;
238                pathBox.NotifyModified(pathBox.master, this);
239                if(dataBox)
240                   dataBox.SaveData();
241                else
242                   pathBox.editBox.SelectAll();
243                if((listBox = pathBoxListBox))
244                   listBox.StopEditing(true);
245             }
246             if(dirsBox) dirsBox.browsing = false;
247             delete this;
248          }
249          return true;
250       }
251    };
252
253    void CheckFileExists()
254    {
255       if(typeExpected != none)
256       {
257          BitmapResource icon = null;
258          char path[MAX_LOCATION];
259          FileAttribs exists;
260          GetSystemPathBuffer(path, editBox.contents);
261          exists = FileExists(path);
262          
263          switch(typeExpected)
264          {
265             case any:
266                // TODO: improvements, add drive, etc, also find a better solution/icon for expect any and file doesn't exist
267                icon = exists ? exists.isFile ? file : exists.isDirectory ? folder : null : null;
268                break;
269             case directory:
270                icon = exists && exists.isDirectory ? folder : brokenFolder;
271                break;
272             case file:
273                icon = exists && exists.isFile ? file : brokenFile;
274                break;
275          }
276
277          picture.image = icon;
278       }
279    }
280
281    void OnRedraw(Surface surface)
282    {
283       if(!isEnabled)
284       {
285          surface.SetBackground(formColor);
286          surface.Area(0, 0, clientSize.w, clientSize.h);
287       }
288       else
289          Window::OnRedraw(surface);
290    }
291
292    ~PathBox()
293    {
294       delete browseDialog;
295    }
296
297 public:
298    virtual bool Window::NotifyModified(PathBox pathBox);
299
300    property PathTypeExpected typeExpected
301    {
302       set
303       {
304          if(value != typeExpected)
305          {
306             if(value == none || typeExpected == none)
307             {
308                bool withIcon = value != none;
309                picture.visible = withIcon;
310                editBox.anchor.left = withIcon ? 18 : 1;
311                CheckFileExists();
312             }
313             typeExpected = value;
314             if(browseDialog && browseDialog.type == open && typeExpected == directory)
315                browseDialog.type = selectDir;
316          }         
317       }
318    }
319
320    property FileDialog browseDialog
321    {
322       set
323       {
324          delete browseDialog;
325          browseDialog = value;
326          if(browseDialog)
327          {
328             incref browseDialog;
329             if(browseDialog.type == open && typeExpected == directory)
330                browseDialog.type = selectDir;
331             if(!strcmp(browseDialog.text, "Select a file...") && text)
332             {
333                char temp[1024] = "Select ";
334                strcat(temp, text);
335                strcat(temp, "...");
336                browseDialog.text = temp;
337             }
338          }
339          browse.visible = browseDialog ? true : false;
340          editBox.anchor.right = browseDialog ? 26 : 1;
341       }
342       get { return browseDialog; }
343    }
344
345    void Home() { editBox.Home(); }
346    void End() { editBox.End(); }
347
348    void SelectAll() { editBox.SelectAll(); }
349    void Deselect() { editBox.Deselect(); }
350
351    property String path
352    {
353       set
354       {
355          char path[MAX_LOCATION];
356          GetSystemPathBuffer(path, value);
357          editBox.contents = path;
358          if(active)
359             editBox.SelectAll();
360          CheckFileExists();
361       }
362       get { return editBox.contents; }
363    }
364    property String slashPath  { get { return GetSlashPathBuffer (path, editBox.contents); } };
365    property String systemPath { get { return GetSystemPathBuffer(path, editBox.contents); } };
366
367    property Color selectionColor { set { editBox.selectionColor = value; } get { return editBox.selectionColor; }/* isset { return selectionColor ? true : false; }*/ };
368    property Color selectionText  { set { editBox.selectionText = value; } get { return editBox.selectionText; }/* isset { return selectionText ? true : false; }*/ };
369 }
370
371 // DirectoriesBox
372 FileDialog browseFileDialog { type = selectDir, text = $"Select directory" };
373
374 public class DirectoriesBox : CommonControl
375 {
376    FileDialog browseDialog;
377    char * baseBrowsePath;
378
379 public:
380
381    bool browsing;
382
383    opacity = 0;
384
385    virtual bool Window::NotifyModified(DirectoriesBox dirsBox);
386    virtual bool Window::NotifyPathBoxModified(DirectoriesBox dirsBox, PathBox pathBox);
387
388    virtual bool OnChangedDir(char ** directory);
389    virtual bool OnPrepareBrowseDir(char ** directory);
390    virtual bool OnBrowsedDir(char ** directory);
391
392    watch(foreground) { list.foreground = foreground; };
393    watch(background) { list.background = background; };
394
395    property Array<String> strings
396    {
397       set
398       {
399          list.Clear();
400          if(value)
401          {
402             for(s : value)
403             {
404                char temp[MAX_LOCATION];
405                list.AddString(GetSystemPathBuffer(temp, s));
406             }
407          }
408          list.AddString("");
409          list.currentRow = list.firstRow;
410          list.modifiedDocument = false;
411       }
412       get
413       {
414          Array<String> array { };
415          DataRow row;
416          for(row = list.firstRow; row; row = row.next)
417          {
418             String string = row.string;
419             if(string && string[0])
420                array.Add(CopyUnixPath(string));
421          }
422          return array;
423       }
424    }
425
426    property FileDialog browseDialog
427    {
428       set
429       {
430          delete browseDialog;
431          browseDialog = value;
432          if(browseDialog)
433          {
434             incref browseDialog;
435             if(browseDialog.type == open)
436                browseDialog.type = selectDir;
437             if(!strcmp(browseDialog.text, "Select a file...") && text)
438             {
439                char temp[1024] = "Select ";
440                strcat(temp, text);
441                strcat(temp, "...");
442                browseDialog.text = temp;
443             }
444          }
445       }
446       get { return browseDialog; }
447    }
448
449    property String baseBrowsePath
450    {
451       set
452       {
453          delete baseBrowsePath;
454          if(value)
455             baseBrowsePath = CopyString(value);
456       }
457       get { return baseBrowsePath; }
458    }
459
460    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
461    {
462       // Browsing was not being set, fixed by introducing dependency to this class to PathBox
463       if(!active && !browsing)
464       {
465          list.StopEditing(true);
466          if(list.modifiedDocument)
467          {
468             NotifyModified(master, this);
469             list.modifiedDocument = false;
470             modifiedDocument = true;
471          }
472       }
473       return true;
474    }
475
476    Button add
477    {
478       parent = this, bevelOver = true, inactive = true;
479       position = { 265, 0 }, size = { 22, 22 };
480       anchor = { top = 0, right = 77 };
481       hotKey = plus, bitmap = BitmapResource { fileName = "<:ecere>actions/listAdd.png", alphaBlend = true };
482       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
483       {
484          list.StopEditing(true);
485          list.lastRow.Edit(null);
486          list.modifiedDocument = true;
487          return true;
488       }
489    };
490    Button remove
491    {
492       parent = this, bevelOver = true, inactive = true;
493       position = { 290, 0 }, size = { 22, 22 };
494       anchor = { top = 0, right = 54 };
495       hotKey = del, bitmap = BitmapResource { fileName = "<:ecere>actions/listRemove.png", alphaBlend = true };
496       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
497       {
498          list.StopEditing(true);
499          if(list.currentRow != list.lastRow)
500          {
501             list.DeleteRow(null);
502             list.modifiedDocument = true;
503          }
504          return true;
505       }
506    };
507    RepButton up
508    {
509       parent = this, bevelOver = true, inactive = true;
510       position = { 315, 0 }, size = { 22, 22 };
511       anchor = { top = 0, right = 31 };
512       hotKey = ctrlUp, bitmap = BitmapResource { fileName = "<:ecere>actions/goUp.png", alphaBlend = true };
513       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
514       {
515          if(list.currentRow != list.lastRow)
516          {
517             DataRow current = list.currentRow, row;
518             if(current)
519             {
520                row = current.previous;
521                if(row)
522                {
523                   row = row.previous;
524                   current.Move(row);
525                   list.modifiedDocument = true;
526                }
527             }
528          }
529          return true;
530       }
531    };
532    RepButton down
533    {
534       parent = this, bevelOver = true, inactive = true;
535       position = { 340, 0 }, size = { 22, 22 };
536       anchor = { top = 0, right = 8 };
537       hotKey = ctrlDown, bitmap = BitmapResource { fileName = "<:ecere>actions/goDown.png", alphaBlend = true };
538       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
539       {
540          DataRow current = list.currentRow, row;
541          if(current)
542          {
543             row = current.next;
544             if(row && row != list.lastRow)
545             {
546                current.Move(row);
547                list.modifiedDocument = true;
548             }
549          }
550          return true;
551       }
552    };
553    ListBox list
554    {
555       this, moveRows = true, hasVertScroll = true, dontHideScroll = true;
556       borderStyle = deep, position = { 0, 22 }, size = { 300, 60 };
557       anchor = { left = 0, top = 22, right = 0, bottom = 0 };
558
559       bool OnRightButtonDown(int x, int y, Modifiers mods)
560       {
561          return parent.OnRightButtonDown(x + position.x + parent.clientStart.x, y + position.y + parent.clientStart.y, mods);
562       }
563
564       bool NotifyChanged(ListBox listBox, DataRow row)
565       {
566          char * directory = listBox.GetData(null);
567          if(directory && directory[0])
568          {
569             char * dir = CopyString(directory);
570             if(OnChangedDir(&dir))
571             {
572                // Put this back to enable making Paths relative by overriding
573                // these DirectoriesBox virtual methods (from FileDialog only)
574                if(browsing)
575                {
576                   OnPrepareBrowseDir(&dir);
577                   OnBrowsedDir(&dir);
578                }
579                listBox.SetData(null, dir);
580                listBox.modifiedDocument = true;
581                if(listBox.currentRow == listBox.lastRow && listBox.lastRow.string)
582                {
583                   DataRow r = listBox.lastRow;
584                   char * s = r.string;
585                   listBox.currentRow = listBox.AddString("");
586                }
587             }
588             delete dir;
589          }
590          else if(listBox.currentRow != listBox.lastRow)
591          {
592             listBox.DeleteRow(null);
593             listBox.modifiedDocument = true;
594          }
595          return true;
596       }
597
598       bool NotifyEditDone(ListBox listBox, DataRow row)
599       {
600          return true;
601       }
602
603       bool NotifyKeyHit(ListBox listBox, DataRow row, Key key, unichar ch)
604       {
605          // Prioritize row moving Ctrl-Up/Down over scrollbar scrolling
606          if(key == ctrlUp || key == ctrlDown)
607          {
608             Button btn = (key == ctrlUp) ? up : down;
609             btn.OnKeyHit(hotKey, 0);
610             return false;
611          }
612          return true;
613       }
614
615       bool NotifyKeyDown(ListBox listBox, DataRow row, Key key, unichar ch)
616       {
617          // Prioritize row moving Ctrl-Up/Down over scrollbar scrolling
618          if(key == ctrlUp || key == ctrlDown)
619          {
620             Button btn = (key == ctrlUp) ? up : down;
621             btn.OnKeyDown(hotKey, 0);
622             return false;
623          }
624          if(key == del)
625          {
626             listBox.StopEditing(true);
627             if(listBox.currentRow != listBox.lastRow)
628                listBox.DeleteRow(null);
629             return false;
630          }
631          return true;
632       }
633
634       bool NotifyMove(ListBox listBox, DataRow row, Modifiers mods)
635       {
636          if(listBox.currentRow == listBox.lastRow)
637             return false;
638          else if(row == listBox.lastRow)
639          {
640             if(listBox.currentRow == row.previous)
641                return false;
642             listBox.currentRow.Move(row.previous);
643             return false;
644          }
645          return true;
646       }
647
648       bool NotifyReclick(ListBox listBox, DataRow row, Modifiers mods)
649       {
650          row.Edit(null);
651          return true;
652       }
653    };
654    DataField dirField { dataType = class(DirPath), editable = true };
655
656    DirectoriesBox()
657    {
658       incref browseFileDialog;
659       browseDialog = browseFileDialog;
660       list.AddField(dirField);
661       list.AddString("");
662       list.modifiedDocument = false;
663    }
664    ~DirectoriesBox()
665    {
666       delete browseDialog;
667       delete baseBrowsePath;
668    }
669 }