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