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