cleaned all trailing white space from source files.
[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       PathBox pathBox
30       {
31          dataBox, borderStyle = 0, anchor = { 0, 0, 0, 0 },
32          typeExpected = any;
33          path = this;
34       };
35       DirectoriesBox dirsBox = pathBoxDirsBox;
36       if(dirsBox)
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 = pathBoxDirsBox;
53          if(dirsBox)
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       PathBox pathBox
72       {
73          dataBox, borderStyle = 0, anchor = { 0, 0, 0, 0 },
74          typeExpected = directory;
75          path = this;
76       };
77       DirectoriesBox dirsBox = pathBoxDirsBox;
78       if(dirsBox)
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 = pathBoxDirsBox;
95          if(dirsBox)
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 = pathBoxDirsBox;
158          if(dirsBox)
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[MAX_LOCATION];
192             char * baseBrowsePath = null;
193             PathBox pathBox = this;
194             DataBox dataBox = pathBoxDataBox;
195             ListBox listBox;
196             DirectoriesBox dirsBox = pathBoxDirsBox;
197             char * ebContents = editBox.contents;
198             String backFilePath = CopyString(browseDialog.filePath);
199
200             browsePath[0] = '\0';
201             strncpy(browsePath, browseDialog.filePath, MAX_LOCATION); browsePath[MAX_LOCATION-1] = '\0';
202             if(dirsBox && dirsBox.baseBrowsePath && dirsBox.baseBrowsePath[0] && ((ebContents && ebContents[0]) || !backFilePath || !backFilePath[0]))
203                PathCat(browsePath, dirsBox.baseBrowsePath);
204             PathCat(browsePath, ebContents);
205             browseDialog.filePath = (ebContents && ebContents[0]) ? browsePath : "";
206             if(pathBox.typeExpected == directory && browsePath[0] && FileExists(browsePath).isDirectory && backFilePath && backFilePath[0])
207                StripLastDirectory(browsePath, browsePath);
208             while(browsePath[0] && !FileExists(browsePath).isDirectory)
209                StripLastDirectory(browsePath, browsePath);
210             if(!browsePath[0])
211             {
212                char path[MAX_LOCATION];
213                LocateModule(null, path);
214                StripLastDirectory(path, path);
215                strncpy(browsePath, path, MAX_LOCATION); browsePath[MAX_LOCATION-1] = '\0';
216             }
217             browseDialog.currentDirectory = browsePath;
218             browseDialog.master = rootWindow;
219
220             incref this;
221             if(dirsBox) dirsBox.browsing = true;
222             if(browseDialog.Modal())
223             {
224                PathBox pathBox = dataBox ? (PathBox)dataBox.editor : this;
225                pathBox.modifiedDocument = true;
226                pathBox.property::path = browseDialog.filePath;
227                pathBox.NotifyModified(pathBox.master, this);
228                if(dataBox)
229                   dataBox.SaveData();
230                else
231                   pathBox.editBox.SelectAll();
232                if((listBox = pathBoxListBox))
233                   listBox.StopEditing(true);
234             }
235             else
236                browseDialog.filePath = backFilePath;
237             delete backFilePath;
238             if(dirsBox) dirsBox.browsing = false;
239             delete this;
240          }
241          return true;
242       }
243    };
244
245    void CheckFileExists()
246    {
247       if(typeExpected != none)
248       {
249          BitmapResource icon = null;
250          char path[MAX_LOCATION];
251          FileAttribs exists { };
252          GetSystemPathBuffer(path, editBox.contents);
253          if(!(path[0] == DIR_SEP && path[1] == DIR_SEP && (!path[2] || !strchr(&path[2], DIR_SEP))))
254             exists = FileExists(path);
255
256          switch(typeExpected)
257          {
258             case any:
259                // TODO: improvements, add drive, etc, also find a better solution/icon for expect any and file doesn't exist
260                icon = exists ? exists.isFile ? file : exists.isDirectory ? folder : null : null;
261                break;
262             case directory:
263                icon = exists && exists.isDirectory ? folder : brokenFolder;
264                break;
265             case file:
266                icon = exists && exists.isFile ? file : brokenFile;
267                break;
268          }
269
270          picture.image = icon;
271       }
272    }
273
274    void OnRedraw(Surface surface)
275    {
276       if(!isEnabled)
277       {
278          surface.SetBackground(formColor);
279          surface.Area(0, 0, clientSize.w, clientSize.h);
280       }
281       else
282          Window::OnRedraw(surface);
283    }
284
285    ~PathBox()
286    {
287       delete browseDialog;
288    }
289
290 public:
291    virtual bool Window::NotifyModified(PathBox pathBox);
292
293    property PathTypeExpected typeExpected
294    {
295       set
296       {
297          if(value != typeExpected)
298          {
299             if(value == none || typeExpected == none)
300             {
301                bool withIcon = value != none;
302                picture.visible = withIcon;
303                editBox.anchor.left = withIcon ? 18 : 1;
304                CheckFileExists();
305             }
306             typeExpected = value;
307             if(browseDialog && browseDialog.type == open && typeExpected == directory)
308                browseDialog.type = selectDir;
309          }
310       }
311    }
312
313    property FileDialog browseDialog
314    {
315       set
316       {
317          delete browseDialog;
318          browseDialog = value;
319          if(browseDialog)
320          {
321             incref browseDialog;
322             if(browseDialog.type == open && typeExpected == directory)
323                browseDialog.type = selectDir;
324             if(!strcmp(browseDialog.text, "Select a file...") && text)
325             {
326                char temp[1024] = "Select ";
327                strcat(temp, text);
328                strcat(temp, "...");
329                browseDialog.text = temp;
330             }
331          }
332          browse.visible = browseDialog ? true : false;
333          editBox.anchor.right = browseDialog ? 26 : 1;
334       }
335       get { return browseDialog; }
336    }
337
338    void Home() { editBox.Home(); }
339    void End() { editBox.End(); }
340
341    void SelectAll() { editBox.SelectAll(); }
342    void Deselect() { editBox.Deselect(); }
343
344    property String path
345    {
346       set
347       {
348          char path[MAX_LOCATION];
349          GetSystemPathBuffer(path, value);
350          editBox.contents = path;
351          if(active)
352             editBox.SelectAll();
353          CheckFileExists();
354       }
355       get { return editBox.contents; }
356    }
357    property String slashPath  { get { return GetSlashPathBuffer (path, editBox.contents); } };
358    property String systemPath { get { return GetSystemPathBuffer(path, editBox.contents); } };
359
360    property Color selectionColor { set { editBox.selectionColor = value; } get { return editBox.selectionColor; }/* isset { return selectionColor ? true : false; }*/ };
361    property Color selectionText  { set { editBox.selectionText = value; } get { return editBox.selectionText; }/* isset { return selectionText ? true : false; }*/ };
362 }
363
364 // DirectoriesBox
365 FileDialog browseFileDialog { type = selectDir, text = $"Select directory" };
366
367 public class DirectoriesBox : CommonControl
368 {
369    FileDialog browseDialog;
370    char * baseBrowsePath;
371
372 public:
373
374    bool browsing;
375
376    opacity = 0;
377
378    virtual bool Window::NotifyModified(DirectoriesBox dirsBox);
379    virtual bool Window::NotifyPathBoxModified(DirectoriesBox dirsBox, PathBox pathBox);
380
381    virtual bool OnChangedDir(char ** directory);
382    virtual bool OnPrepareBrowseDir(char ** directory);
383    virtual bool OnBrowsedDir(char ** directory);
384
385    watch(foreground) { list.foreground = foreground; };
386    watch(background) { list.background = background; };
387
388    property Array<String> strings
389    {
390       set
391       {
392          list.Clear();
393          if(value)
394          {
395             for(s : value)
396             {
397                char temp[MAX_LOCATION];
398                list.AddString(GetSystemPathBuffer(temp, s));
399             }
400          }
401          list.AddString("");
402          list.currentRow = list.firstRow;
403          list.modifiedDocument = false;
404       }
405       get
406       {
407          Array<String> array { };
408          DataRow row;
409          for(row = list.firstRow; row; row = row.next)
410          {
411             String string = row.string;
412             if(string && string[0])
413                array.Add(CopyUnixPath(string));
414          }
415          return array;
416       }
417    }
418
419    property FileDialog browseDialog
420    {
421       set
422       {
423          delete browseDialog;
424          browseDialog = value;
425          if(browseDialog)
426          {
427             incref browseDialog;
428             if(browseDialog.type == open)
429                browseDialog.type = selectDir;
430             if(!strcmp(browseDialog.text, "Select a file...") && text)
431             {
432                char temp[1024] = "Select ";
433                strcat(temp, text);
434                strcat(temp, "...");
435                browseDialog.text = temp;
436             }
437          }
438       }
439       get { return browseDialog; }
440    }
441
442    property String baseBrowsePath
443    {
444       set
445       {
446          delete baseBrowsePath;
447          if(value)
448             baseBrowsePath = CopyString(value);
449       }
450       get { return baseBrowsePath; }
451    }
452
453    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
454    {
455       // Browsing was not being set, fixed by introducing dependency to this class to PathBox
456       if(!active && !browsing)
457       {
458          list.StopEditing(true);
459          if(list.modifiedDocument)
460          {
461             NotifyModified(master, this);
462             list.modifiedDocument = false;
463             modifiedDocument = true;
464          }
465       }
466       return true;
467    }
468
469    Button add
470    {
471       parent = this, bevelOver = true, inactive = true;
472       position = { 265, 0 }, size = { 22, 22 };
473       anchor = { top = 0, right = 77 };
474       hotKey = plus, bitmap = BitmapResource { fileName = "<:ecere>actions/listAdd.png", alphaBlend = true };
475       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
476       {
477          list.StopEditing(true);
478          list.lastRow.Edit(null);
479          list.modifiedDocument = true;
480          return true;
481       }
482    };
483    Button remove
484    {
485       parent = this, bevelOver = true, inactive = true;
486       position = { 290, 0 }, size = { 22, 22 };
487       anchor = { top = 0, right = 54 };
488       hotKey = del, bitmap = BitmapResource { fileName = "<:ecere>actions/listRemove.png", alphaBlend = true };
489       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
490       {
491          list.StopEditing(true);
492          if(list.currentRow != list.lastRow)
493          {
494             list.DeleteRow(null);
495             list.modifiedDocument = true;
496          }
497          return true;
498       }
499    };
500    RepButton up
501    {
502       parent = this, bevelOver = true, inactive = true;
503       position = { 315, 0 }, size = { 22, 22 };
504       anchor = { top = 0, right = 31 };
505       hotKey = ctrlUp, bitmap = BitmapResource { fileName = "<:ecere>actions/goUp.png", alphaBlend = true };
506       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
507       {
508          if(list.currentRow != list.lastRow)
509          {
510             DataRow current = list.currentRow, row;
511             if(current)
512             {
513                row = current.previous;
514                if(row)
515                {
516                   row = row.previous;
517                   current.Move(row);
518                   list.modifiedDocument = true;
519                }
520             }
521          }
522          return true;
523       }
524    };
525    RepButton down
526    {
527       parent = this, bevelOver = true, inactive = true;
528       position = { 340, 0 }, size = { 22, 22 };
529       anchor = { top = 0, right = 8 };
530       hotKey = ctrlDown, bitmap = BitmapResource { fileName = "<:ecere>actions/goDown.png", alphaBlend = true };
531       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
532       {
533          DataRow current = list.currentRow, row;
534          if(current)
535          {
536             row = current.next;
537             if(row && row != list.lastRow)
538             {
539                current.Move(row);
540                list.modifiedDocument = true;
541             }
542          }
543          return true;
544       }
545    };
546    ListBox list
547    {
548       this, moveRows = true, hasVertScroll = true, dontHideScroll = true;
549       borderStyle = deep, position = { 0, 22 }, size = { 300, 60 };
550       anchor = { left = 0, top = 22, right = 0, bottom = 0 };
551
552       bool OnRightButtonDown(int x, int y, Modifiers mods)
553       {
554          return parent.OnRightButtonDown(x + position.x + parent.clientStart.x, y + position.y + parent.clientStart.y, mods);
555       }
556
557       bool NotifyChanged(ListBox listBox, DataRow row)
558       {
559          char * directory = listBox.GetData(null);
560          if(directory && directory[0])
561          {
562             char * dir = CopyString(directory);
563             if(OnChangedDir(&dir))
564             {
565                // Put this back to enable making Paths relative by overriding
566                // these DirectoriesBox virtual methods (from FileDialog only)
567                if(browsing)
568                {
569                   OnPrepareBrowseDir(&dir);
570                   OnBrowsedDir(&dir);
571                }
572                listBox.SetData(null, dir);
573                listBox.modifiedDocument = true;
574                if(listBox.currentRow == listBox.lastRow && listBox.lastRow.string)
575                {
576                   DataRow r = listBox.lastRow;
577                   char * s = r.string;
578                   listBox.currentRow = listBox.AddString("");
579                }
580             }
581             delete dir;
582          }
583          else if(listBox.currentRow != listBox.lastRow)
584          {
585             listBox.DeleteRow(null);
586             listBox.modifiedDocument = true;
587          }
588          return true;
589       }
590
591       bool NotifyEditDone(ListBox listBox, DataRow row)
592       {
593          return true;
594       }
595
596       bool NotifyKeyHit(ListBox listBox, DataRow row, Key key, unichar ch)
597       {
598          // Prioritize row moving Ctrl-Up/Down over scrollbar scrolling
599          if(key == ctrlUp || key == ctrlDown)
600          {
601             Button btn = (key == ctrlUp) ? up : down;
602             btn.OnKeyHit(hotKey, 0);
603             return false;
604          }
605          return true;
606       }
607
608       bool NotifyKeyDown(ListBox listBox, DataRow row, Key key, unichar ch)
609       {
610          // Prioritize row moving Ctrl-Up/Down over scrollbar scrolling
611          if(key == ctrlUp || key == ctrlDown)
612          {
613             Button btn = (key == ctrlUp) ? up : down;
614             btn.OnKeyDown(hotKey, 0);
615             return false;
616          }
617          if(key == del)
618          {
619             listBox.StopEditing(true);
620             if(listBox.currentRow != listBox.lastRow)
621                listBox.DeleteRow(null);
622             return false;
623          }
624          return true;
625       }
626
627       bool NotifyMove(ListBox listBox, DataRow row, Modifiers mods)
628       {
629          if(listBox.currentRow == listBox.lastRow)
630             return false;
631          else if(row == listBox.lastRow)
632          {
633             if(listBox.currentRow == row.previous)
634                return false;
635             listBox.currentRow.Move(row.previous);
636             return false;
637          }
638          return true;
639       }
640
641       bool NotifyReclick(ListBox listBox, DataRow row, Modifiers mods)
642       {
643          row.Edit(null);
644          return true;
645       }
646    };
647    DataField dirField { dataType = class(DirPath), editable = true };
648
649    DirectoriesBox()
650    {
651       incref browseFileDialog;
652       browseDialog = browseFileDialog;
653       list.AddField(dirField);
654       list.AddString("");
655       list.modifiedDocument = false;
656    }
657    ~DirectoriesBox()
658    {
659       delete browseDialog;
660       delete baseBrowsePath;
661    }
662 }