1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 using System.IO;
  9 
 10 namespace System.Screen
 11 {
 12     public enum FileDialogKind
 13     {
 14         openDialogsaveDialog
 15     }
 16 
 17     public const uchar folderSymbol = cast<uchar>(0x1F5C0);
 18     public const uchar emptyDocumentSymbol = cast<uchar>(0x1F5CB);
 19 
 20     public class DirectoryItem
 21     {
 22         public nothrow DirectoryItem(const DirectoryEntry& entry_const FileStatus& status_) : entry(entry_)status(status_)
 23         {
 24         }
 25         public DirectoryEntry entry;
 26         public FileStatus status;
 27     }
 28 
 29     public class DirectoryItemLess : Rel<DirectoryItem>
 30     {
 31         public nothrow bool operator()(const DirectoryItem& leftconst DirectoryItem& right) const
 32         {
 33             if (left.status.fileType == FileType.directory)
 34             {
 35                 if (right.status.fileType == FileType.directory)
 36                 {
 37                     return left.entry.name < right.entry.name;
 38                 }
 39                 else
 40                 {
 41                     return true;
 42                 }
 43             }
 44             else if (left.status.fileType == FileType.regular)
 45             {
 46                 if (right.status.fileType == FileType.directory)
 47                 {
 48                     return false;
 49                 }
 50                 else if (right.status.fileType == FileType.regular)
 51                 {
 52                     return left.entry.name < right.entry.name;
 53                 }
 54                 else
 55                 {
 56                     return true;
 57                 }
 58             }
 59             else if (left.status.fileType == FileType.fifo)
 60             {
 61                 if (right.status.fileType == FileType.directory)
 62                 {
 63                     return false;
 64                 }
 65                 else if (right.status.fileType == FileType.regular)
 66                 {
 67                     return false;
 68                 }
 69                 else
 70                 {
 71                     return left.entry.name < right.entry.name;
 72                 }
 73             }
 74             return false;
 75         }
 76     }
 77     
 78     public DirectoryItem MakeItem(const string& directoryconst DirectoryEntry& entry)
 79     {
 80         string path = Path.Combine(directoryentry.name);
 81         FileStatus status;
 82         Stat(path.Chars()status);
 83         return DirectoryItem(entrystatus);
 84     }
 85     
 86     public class FileDialogCreateParams
 87     {
 88         public nothrow FileDialogCreateParams() : controlCreateParams()kind(FileDialogKind.openDialog)caption()directory(GetCurrentWorkingDirectory())requireFileExists(false)
 89         {
 90         }
 91         public nothrow FileDialogCreateParams& Defaults()
 92         {
 93             return *this;
 94         }
 95         public nothrow FileDialogCreateParams& SetKind(FileDialogKind kind_)
 96         {
 97             kind = kind_;
 98             return *this;
 99         }
100         public nothrow FileDialogCreateParams& SetCaption(const string& caption_)
101         {
102             caption = caption_;
103             return *this;
104         }
105         public nothrow FileDialogCreateParams& SetDirectory(const string& directory_)
106         {
107             directory = directory_;
108             return *this;
109         }
110         public nothrow FileDialogCreateParams& SetRequireFileExists(bool requireFileExists_)
111         {
112             requireFileExists = requireFileExists_;
113             return *this;
114         }
115         public ControlCreateParams controlCreateParams;
116         public FileDialogKind kind;
117         public string caption;
118         public string directory;
119         public bool requireFileExists;
120     }
121 
122     public class FileDialog : Window
123     {
124         public nothrow FileDialog(FileDialogCreateParams& createParams) : 
125             base(createParams.controlCreateParams)
126             kind(createParams.kind)
127             caption(createParams.caption)
128             directory(createParams.directory)
129             requireFileExists(createParams.requireFileExists)
130         {
131             InvalidateGuard guard(thisInvalidateKind.invalidateIfNotDefault);
132             if (caption.IsEmpty())
133             {
134                 switch (kind)
135                 {
136                     case FileDialogKind.openDialog:
137                     {
138                         caption = "Open File";
139                         break;
140                     }
141                     case FileDialogKind.saveDialog:
142                     {
143                         caption = "Save File";
144                         break;
145                     }
146                 }
147             }
148             bool setLoc = false;
149             if (Location().IsDefault())
150             {
151                 SetLocation(Point(00));
152                 setLoc = true;
153             }
154             if (GetSize().IsDefault())
155             {
156                 SetSize(Size(6020));
157             }
158             if (ForeColor() == ConsoleColor.defaultColor)
159             {
160                 SetForeColor(ConsoleColor.black);
161             }
162             if (BackColor() == ConsoleColor.defaultColor)
163             {
164                 SetBackColor(ConsoleColor.gray);
165             }
166             Measure(setLoc);
167             Point loc = Location();
168             Size sz = GetSize();
169             Label* label = new Label(LabelCreateParams().SetLocation(Point(loc.x + 2loc.y + 1)).SetText("Directory:"));
170             AddChild(label);
171             directoryLabel = new Label(LabelCreateParams().SetLocation(Point(loc.x + 2loc.y + 2)).SetText(directory));
172             AddChild(directoryLabel);
173             listBox = new ListBox(ListBoxCreateParams().SetLocation(Point(loc.x + 2loc.y + 4)).SetSize(Size(sz.w - 413)));
174             listBox->SelectedIndexChangedEvent().AddHandler(ListBoxSelectedIndexChanged);
175             listBox->ItemSelectedEvent().AddHandler(ListBoxItemSelected);
176             AddChild(listBox);
177             textBox = new TextBox(TextBoxCreateParams().SetLocation(Point(loc.x + 2loc.y + 18)).SetSize(Size(sz.w - 41)));
178             textBox->TextChangedEvent().AddHandler(TextBoxTextChanged);
179             textBox->TextEnteredEvent().AddHandler(TextBoxTextEntered);
180             AddChild(textBox);
181             string buttonText;
182             switch (kind)
183             {
184                 case FileDialogKind.openDialog:
185                 {
186                     buttonText = "[ &Open ]";
187                     break;
188                 }
189                 case FileDialogKind.saveDialog:
190                 {
191                     buttonText = "[ &Save ]";
192                     break;
193                 }
194             }
195             defaultButton = new Button(ButtonCreateParams().Text(buttonText));
196             defaultButton->SetDialogResult(DialogResult.ok);
197             defaultButton->SetDefault();
198             AddChild(defaultButton);
199             Button* cancelButton = Button.Cancel();
200             AddChild(cancelButton);
201             cancelButton->SetLocation(Point(loc.x + sz.w - cancelButton->GetSize().w - 1loc.y + sz.h - 2));
202             defaultButton->SetLocation(Point(cancelButton->Location().x - defaultButton->GetSize().w - 1cancelButton->Location().y));
203             MakeListBoxContent();
204             Control* firstFocusableControl = FirstFocusabledControl();
205             if (firstFocusableControl != null)
206             {
207                 firstFocusableControl->SetFocus();
208             }
209             SetDefaultButtonStatus();
210         }
211         public string FilePath() const
212         {
213             if (!textBox->Text().IsEmpty())
214             {
215                 return Path.Combine(directoryToUtf8(textBox->Text()));
216             }
217             else if (listBox->SelectedIndex() >= 0 && listBox->SelectedIndex() < listBox->ItemCount())
218             {
219                 if (items[listBox->SelectedIndex()].status.fileType == FileType.regular)
220                 {
221                     return Path.Combine(directoryitems[listBox->SelectedIndex()].entry.name);
222                 }
223             }
224             return string();
225         }
226         public override void OnWriteScreen(WriteScreenEventArgs& args)
227         {
228             base->OnWriteScreen(args);
229             Rect updateRect = GetRect();
230             bool isDefault = args.GetRect().IsDefault();
231             if (!isDefault)
232             {
233                 updateRect = Rect.Intersection(updateRectargs.GetRect());
234             }
235             if (updateRect.IsEmpty()) return;
236             if (isDefault)
237             {
238                 WriteBox(updateRectForeColor()BackColor());
239                 if (!caption.IsEmpty())
240                 {
241                     WriteCaption(u" " + ToUtf32(caption) + u" ");
242                 }
243             }
244         }
245         private void ListBoxItemSelected()
246         {
247             if (items[listBox->SelectedIndex()].status.fileType == FileType.directory)
248             {
249                 InvalidateGuard listBoxGuard(listBoxInvalidateKind.forceInvalidate);
250                 InvalidateGuard directoryLabelGuard(directoryLabelInvalidateKind.forceInvalidate);
251                 directory = GetFullPath(Path.Combine(directoryitems[listBox->SelectedIndex()].entry.name));
252                 directoryLabel->SetText(directory);
253                 MakeListBoxContent();
254             }
255             else if (items[listBox->SelectedIndex()].status.fileType == FileType.regular)
256             {
257                 SetDefaultButtonStatus();
258                 defaultButton->Press();
259             }
260         }
261         private void ListBoxSelectedIndexChanged()
262         {
263             if (requireFileExists)
264             {
265                 if (items[listBox->SelectedIndex()].status.fileType == FileType.directory)
266                 {
267                     defaultButton->SetEnabled();
268                 }
269                 else if (items[listBox->SelectedIndex()].status.fileType == FileType.regular)
270                 {
271                     SetDefaultButtonStatus();
272                 }
273                 else if (items[listBox->SelectedIndex()].status.fileType == FileType.fifo)
274                 {
275                     defaultButton->SetDisabled();
276                 }
277             }
278             else if (items[listBox->SelectedIndex()].status.fileType == FileType.regular)
279             {
280                 defaultButton->SetEnabled();
281             }
282             else if (items[listBox->SelectedIndex()].status.fileType == FileType.fifo)
283             {
284                 defaultButton->SetDisabled();
285             }
286         }
287         private void TextBoxTextChanged()
288         {
289             if (!textBox->Text().IsEmpty())
290             {
291                 SetDefaultButtonStatus();
292             }
293             else
294             {
295                 defaultButton->SetDisabled();
296             }
297         }
298         private void TextBoxTextEntered()
299         {
300             if (!textBox->Text().IsEmpty())
301             {
302                 SetDefaultButtonStatus();
303                 defaultButton->Press();
304             }
305         }
306         private void SetDefaultButtonStatus()
307         {
308             if (requireFileExists)
309             {
310                 string filePath = FilePath();
311                 if (!filePath.IsEmpty() && File.Exists(filePath))
312                 {
313                     defaultButton->SetEnabled();
314                 }
315                 else
316                 {
317                     defaultButton->SetDisabled();
318                 }
319             }
320             else
321             {
322                 string filePath = FilePath();
323                 if (!filePath.IsEmpty())
324                 {
325                     defaultButton->SetEnabled();
326                 }
327                 else
328                 {
329                     defaultButton->SetDisabled();
330                 }
331             }
332         }
333         private void Measure(bool setLoc)
334         {
335             Rect rect = GetRect();
336             rect.Inflate(11);
337             SetLocation(rect.location);
338             SetSize(rect.size);
339             Size sz = GetSize();
340             if (setLoc)
341             {
342                 int x = Max(cast<int>(0)(TerminalWindowWidth() - sz.w) / 2);
343                 int y = Max(cast<int>(0)(TerminalWindowHeight() - sz.h) / 2);
344                 SetLocation(Point(xy));
345             }
346         }
347         private void WriteCaption(const ustring& captionStr)
348         {
349             Point loc = Location();
350             Size sz = GetSize();
351             int offset = Max(cast<int>(0)cast<int>(sz.w - captionStr.Length()));
352             int x = loc.x + offset / 2;
353             int y = loc.y;
354             SetCursorPos(xy);
355             Terminal.Out() << captionStr;
356         }
357         private void MakeListBoxContent()
358         {
359             ReadDirectory();
360             SortItems();
361             AddItemsToListBox();
362         }
363         private void AddItemsToListBox()
364         {
365             listBox->Clear();
366             Size sz = listBox->GetSize();
367             int width = sz.w;
368             for (const DirectoryItem& item : items)
369             {
370                 ustring itemStr;
371                 if (item.status.fileType == FileType.directory)
372                 {
373                     itemStr.Append(' ').Append(folderSymbol).Append(' ');
374                 }
375                 else if (item.status.fileType == FileType.regular)
376                 {
377                     itemStr.Append(' ').Append(emptyDocumentSymbol).Append(' ');
378                 }
379                 else if (item.status.fileType == FileType.fifo)
380                 {
381                     itemStr.Append(' ').Append(emptyDocumentSymbol).Append(' ');
382                 }
383                 ustring entryName = ToUtf32(item.entry.name);
384                 int entryWidth = width - 3;
385                 itemStr.Append(entryName.Substring(0entryWidth));
386                 int spaceWidth = Max(cast<int>(0)cast<int>(entryWidth - entryName.Length()));
387                 if (spaceWidth > 0)
388                 {
389                     itemStr.Append(ustring(' 'spaceWidth));
390                 }
391                 listBox->AddItem(itemStr);
392             }
393             listBox->SetSelectedIndex(0);
394         }
395         private void ReadDirectory()
396         {
397             items.Clear();
398             DirectoryReader reader(directory);
399             DirectoryEntry entry;
400             while (reader.Read(entry))
401             {
402                 if (entry.IsDot())
403                 {
404                     continue;
405                 }
406                 else if (entry.IsDotDot())
407                 {
408                     if (directory != "/")
409                     {
410                         items.Add(MakeItem(directoryentry));
411                     }
412                 }
413                 else if (directory == "/" && entry.name == "dev")
414                 {
415                     continue;
416                 }
417                 else
418                 {
419                     items.Add(MakeItem(directoryentry));
420                 }
421             }
422         }
423         private void SortItems()
424         {
425             Sort(items.Begin()items.End()DirectoryItemLess());
426         }
427         private FileDialogKind kind;
428         private string caption;
429         private string directory;
430         private Label* directoryLabel;
431         private ListBox* listBox;
432         private TextBox* textBox;
433         private Button* defaultButton;
434         private List<DirectoryItem> items;
435         private bool requireFileExists;
436     }
437 }