FileDialog.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. //
  2. // FileDialog.cs: File system dialogs for open and save
  3. //
  4. // TODO:
  5. // * Add directory selector
  6. // * Implement subclasses
  7. // * Figure out why message text does not show
  8. // * Remove the extra space when message does not show
  9. // * Use a line separator to show the file listing, so we can use same colors as the rest
  10. // * DirListView: Add mouse support
  11. using System;
  12. using System.Collections.Generic;
  13. using NStack;
  14. using System.IO;
  15. using System.Linq;
  16. namespace Terminal.Gui {
  17. internal class DirListView : View {
  18. int top, selected;
  19. DirectoryInfo dirInfo;
  20. List<(string, bool, bool)> infos;
  21. internal bool canChooseFiles = true;
  22. internal bool canChooseDirectories = false;
  23. internal bool allowsMultipleSelection = false;
  24. FileDialog host;
  25. public DirListView (FileDialog host)
  26. {
  27. infos = new List<(string, bool, bool)> ();
  28. CanFocus = true;
  29. this.host = host;
  30. }
  31. bool IsAllowed (FileSystemInfo fsi)
  32. {
  33. if (fsi.Attributes.HasFlag (FileAttributes.Directory))
  34. return true;
  35. if (allowedFileTypes == null)
  36. return true;
  37. foreach (var ft in allowedFileTypes)
  38. if (fsi.Name.EndsWith (ft))
  39. return true;
  40. return false;
  41. }
  42. internal bool Reload (ustring value = null)
  43. {
  44. bool valid = false;
  45. try {
  46. dirInfo = new DirectoryInfo (value == null ? directory.ToString () : value.ToString ());
  47. infos = (from x in dirInfo.GetFileSystemInfos ()
  48. where IsAllowed (x) && (!canChooseFiles ? x.Attributes.HasFlag (FileAttributes.Directory) : true)
  49. orderby (!x.Attributes.HasFlag (FileAttributes.Directory)) + x.Name
  50. select (x.Name, x.Attributes.HasFlag (FileAttributes.Directory), false)).ToList ();
  51. infos.Insert (0, ("..", true, false));
  52. top = 0;
  53. selected = 0;
  54. valid = true;
  55. } catch (Exception ex) {
  56. switch (ex) {
  57. case DirectoryNotFoundException _:
  58. case ArgumentException _:
  59. dirInfo = null;
  60. infos.Clear ();
  61. valid = true;
  62. break;
  63. default:
  64. valid = false;
  65. break;
  66. }
  67. } finally {
  68. if (valid) {
  69. SetNeedsDisplay ();
  70. }
  71. }
  72. return valid;
  73. }
  74. ustring directory;
  75. public ustring Directory {
  76. get => directory;
  77. set {
  78. if (directory == value) {
  79. return;
  80. }
  81. if (Reload (value)) {
  82. directory = value;
  83. }
  84. }
  85. }
  86. public override void PositionCursor ()
  87. {
  88. Move (0, selected - top);
  89. }
  90. int lastSelected;
  91. bool shiftOnWheel;
  92. public override bool MouseEvent (MouseEvent me)
  93. {
  94. if ((me.Flags & (MouseFlags.Button1Clicked | MouseFlags.Button1DoubleClicked |
  95. MouseFlags.WheeledUp | MouseFlags.WheeledDown)) == 0)
  96. return false;
  97. if (!HasFocus)
  98. SetFocus ();
  99. if (infos == null)
  100. return false;
  101. if (me.Y + top >= infos.Count)
  102. return true;
  103. int lastSelectedCopy = shiftOnWheel ? lastSelected : selected;
  104. switch (me.Flags) {
  105. case MouseFlags.Button1Clicked:
  106. SetSelected (me);
  107. OnSelectionChanged ();
  108. SetNeedsDisplay ();
  109. break;
  110. case MouseFlags.Button1DoubleClicked:
  111. UnMarkAll ();
  112. SetSelected (me);
  113. if (ExecuteSelection ()) {
  114. host.canceled = false;
  115. Application.RequestStop ();
  116. }
  117. return true;
  118. case MouseFlags.Button1Clicked | MouseFlags.ButtonShift:
  119. SetSelected (me);
  120. if (shiftOnWheel)
  121. lastSelected = lastSelectedCopy;
  122. shiftOnWheel = false;
  123. PerformMultipleSelection (lastSelected);
  124. return true;
  125. case MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl:
  126. SetSelected (me);
  127. PerformMultipleSelection ();
  128. return true;
  129. case MouseFlags.WheeledUp:
  130. SetSelected (me);
  131. selected = lastSelected;
  132. MoveUp ();
  133. return true;
  134. case MouseFlags.WheeledDown:
  135. SetSelected (me);
  136. selected = lastSelected;
  137. MoveDown ();
  138. return true;
  139. case MouseFlags.WheeledUp | MouseFlags.ButtonShift:
  140. SetSelected (me);
  141. selected = lastSelected;
  142. lastSelected = lastSelectedCopy;
  143. shiftOnWheel = true;
  144. MoveUp ();
  145. return true;
  146. case MouseFlags.WheeledDown | MouseFlags.ButtonShift:
  147. SetSelected (me);
  148. selected = lastSelected;
  149. lastSelected = lastSelectedCopy;
  150. shiftOnWheel = true;
  151. MoveDown ();
  152. return true;
  153. }
  154. return true;
  155. }
  156. private void UnMarkAll ()
  157. {
  158. if (allowsMultipleSelection && infos.Count > 0) {
  159. for (int i = 0; i < infos.Count; i++) {
  160. if (infos [i].Item3) {
  161. infos [i] = (infos [i].Item1, infos [i].Item2, false);
  162. }
  163. }
  164. }
  165. }
  166. void SetSelected (MouseEvent me)
  167. {
  168. lastSelected = selected;
  169. selected = top + me.Y;
  170. }
  171. void DrawString (int line, string str)
  172. {
  173. var f = Frame;
  174. var width = f.Width;
  175. var ustr = ustring.Make (str);
  176. Move (allowsMultipleSelection ? 3 : 2, line);
  177. int byteLen = ustr.Length;
  178. int used = allowsMultipleSelection ? 2 : 1;
  179. for (int i = 0; i < byteLen;) {
  180. (var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
  181. var count = Rune.ColumnWidth (rune);
  182. if (used + count >= width)
  183. break;
  184. Driver.AddRune (rune);
  185. used += count;
  186. i += size;
  187. }
  188. for (; used < width - 1; used++) {
  189. Driver.AddRune (' ');
  190. }
  191. }
  192. public override void Redraw (Rect bounds)
  193. {
  194. var current = ColorScheme.Focus;
  195. Driver.SetAttribute (current);
  196. Move (0, 0);
  197. var f = Frame;
  198. var item = top;
  199. bool focused = HasFocus;
  200. var width = bounds.Width;
  201. for (int row = 0; row < f.Height; row++, item++) {
  202. bool isSelected = item == selected;
  203. Move (0, row);
  204. var newcolor = focused ? (isSelected ? ColorScheme.HotNormal : ColorScheme.Focus) : ColorScheme.Focus;
  205. if (newcolor != current) {
  206. Driver.SetAttribute (newcolor);
  207. current = newcolor;
  208. }
  209. if (item >= infos.Count) {
  210. for (int c = 0; c < f.Width; c++)
  211. Driver.AddRune (' ');
  212. continue;
  213. }
  214. var fi = infos [item];
  215. Driver.AddRune (isSelected ? '>' : ' ');
  216. if (allowsMultipleSelection)
  217. Driver.AddRune (fi.Item3 ? '*' : ' ');
  218. if (fi.Item2)
  219. Driver.AddRune ('/');
  220. else
  221. Driver.AddRune (' ');
  222. DrawString (row, fi.Item1);
  223. }
  224. }
  225. public Action<(string, bool)> SelectedChanged { get; set; }
  226. public Action<ustring> DirectoryChanged { get; set; }
  227. public Action<ustring> FileChanged { get; set; }
  228. void OnSelectionChanged ()
  229. {
  230. if (allowsMultipleSelection) {
  231. if (FilePaths.Count > 0) {
  232. FileChanged?.Invoke (string.Join (", ", GetFilesName (FilePaths)));
  233. } else {
  234. FileChanged?.Invoke (infos [selected].Item2 && !canChooseDirectories ? "" : Path.GetFileName (infos [selected].Item1));
  235. }
  236. } else {
  237. var sel = infos [selected];
  238. SelectedChanged?.Invoke ((sel.Item1, sel.Item2));
  239. }
  240. }
  241. List<string> GetFilesName (IReadOnlyList<string> files)
  242. {
  243. List<string> filesName = new List<string> ();
  244. foreach (var file in files) {
  245. filesName.Add (Path.GetFileName (file));
  246. }
  247. return filesName;
  248. }
  249. public override bool ProcessKey (KeyEvent keyEvent)
  250. {
  251. switch (keyEvent.Key) {
  252. case Key.CursorUp:
  253. case Key.P | Key.CtrlMask:
  254. MoveUp ();
  255. return true;
  256. case Key.CursorDown:
  257. case Key.N | Key.CtrlMask:
  258. MoveDown ();
  259. return true;
  260. case Key.V | Key.CtrlMask:
  261. case Key.PageDown:
  262. var n = (selected + Frame.Height);
  263. if (n > infos.Count)
  264. n = infos.Count - 1;
  265. if (n != selected) {
  266. selected = n;
  267. if (infos.Count >= Frame.Height)
  268. top = selected;
  269. else
  270. top = 0;
  271. OnSelectionChanged ();
  272. SetNeedsDisplay ();
  273. }
  274. return true;
  275. case Key.Enter:
  276. UnMarkAll ();
  277. if (ExecuteSelection ())
  278. return false;
  279. else
  280. return true;
  281. case Key.PageUp:
  282. n = (selected - Frame.Height);
  283. if (n < 0)
  284. n = 0;
  285. if (n != selected) {
  286. selected = n;
  287. top = selected;
  288. OnSelectionChanged ();
  289. SetNeedsDisplay ();
  290. }
  291. return true;
  292. case Key.Space:
  293. case Key.T | Key.CtrlMask:
  294. PerformMultipleSelection ();
  295. return true;
  296. case Key.Home:
  297. MoveFirst ();
  298. return true;
  299. case Key.End:
  300. MoveLast ();
  301. return true;
  302. }
  303. return base.ProcessKey (keyEvent);
  304. }
  305. void MoveLast ()
  306. {
  307. selected = infos.Count - 1;
  308. top = infos.Count () - 1;
  309. OnSelectionChanged ();
  310. SetNeedsDisplay ();
  311. }
  312. void MoveFirst ()
  313. {
  314. selected = 0;
  315. top = 0;
  316. OnSelectionChanged ();
  317. SetNeedsDisplay ();
  318. }
  319. void MoveDown ()
  320. {
  321. if (selected + 1 < infos.Count) {
  322. selected++;
  323. if (selected >= top + Frame.Height)
  324. top++;
  325. OnSelectionChanged ();
  326. SetNeedsDisplay ();
  327. }
  328. }
  329. void MoveUp ()
  330. {
  331. if (selected > 0) {
  332. selected--;
  333. if (selected < top)
  334. top = selected;
  335. OnSelectionChanged ();
  336. SetNeedsDisplay ();
  337. }
  338. }
  339. internal bool ExecuteSelection ()
  340. {
  341. if (infos.Count == 0) {
  342. return false;
  343. }
  344. var isDir = infos [selected].Item2;
  345. if (isDir) {
  346. Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1));
  347. DirectoryChanged?.Invoke (Directory);
  348. } else {
  349. FileChanged?.Invoke (infos [selected].Item1);
  350. if (canChooseFiles) {
  351. // Ensures that at least one file is selected.
  352. if (FilePaths.Count == 0)
  353. PerformMultipleSelection ();
  354. // Let the OK handler take it over
  355. return true;
  356. }
  357. // No files allowed, do not let the default handler take it.
  358. }
  359. return false;
  360. }
  361. void PerformMultipleSelection (int? firstSelected = null)
  362. {
  363. if (allowsMultipleSelection) {
  364. int first = Math.Min (firstSelected ?? selected, selected);
  365. int last = Math.Max (selected, firstSelected ?? selected);
  366. for (int i = first; i <= last; i++) {
  367. if ((canChooseFiles && infos [i].Item2 == false) ||
  368. (canChooseDirectories && infos [i].Item2 &&
  369. infos [i].Item1 != "..")) {
  370. infos [i] = (infos [i].Item1, infos [i].Item2, !infos [i].Item3);
  371. }
  372. }
  373. OnSelectionChanged ();
  374. SetNeedsDisplay ();
  375. }
  376. }
  377. string [] allowedFileTypes;
  378. public string [] AllowedFileTypes {
  379. get => allowedFileTypes;
  380. set {
  381. allowedFileTypes = value;
  382. Reload ();
  383. }
  384. }
  385. public string MakePath (string relativePath)
  386. {
  387. var dir = Directory.ToString ();
  388. return string.IsNullOrEmpty (dir) ? "" : Path.GetFullPath (Path.Combine (dir, relativePath));
  389. }
  390. public IReadOnlyList<string> FilePaths {
  391. get {
  392. if (allowsMultipleSelection) {
  393. var res = new List<string> ();
  394. foreach (var item in infos) {
  395. if (item.Item3)
  396. res.Add (MakePath (item.Item1));
  397. }
  398. if (res.Count == 0 && infos.Count > 0 && infos [selected].Item1 != "..") {
  399. res.Add (MakePath (infos [selected].Item1));
  400. }
  401. return res;
  402. } else {
  403. if (infos.Count == 0) {
  404. return null;
  405. }
  406. if (infos [selected].Item2) {
  407. if (canChooseDirectories) {
  408. var sel = infos [selected].Item1;
  409. return sel == ".." ? new List<string> () : new List<string> () { MakePath (infos [selected].Item1) };
  410. }
  411. return Array.Empty<string> ();
  412. } else {
  413. if (canChooseFiles) {
  414. return new List<string> () { MakePath (infos [selected].Item1) };
  415. }
  416. return Array.Empty<string> ();
  417. }
  418. }
  419. }
  420. }
  421. ///<inheritdoc/>
  422. public override bool OnEnter (View view)
  423. {
  424. Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
  425. return base.OnEnter (view);
  426. }
  427. }
  428. /// <summary>
  429. /// Base class for the <see cref="OpenDialog"/> and the <see cref="SaveDialog"/>
  430. /// </summary>
  431. public class FileDialog : Dialog {
  432. Button prompt, cancel;
  433. Label nameFieldLabel, message, dirLabel;
  434. TextField dirEntry, nameEntry;
  435. internal DirListView dirListView;
  436. /// <summary>
  437. /// Initializes a new <see cref="FileDialog"/>.
  438. /// </summary>
  439. public FileDialog () : this (title: string.Empty, prompt: string.Empty, nameFieldLabel: string.Empty, message: string.Empty) { }
  440. /// <summary>
  441. /// Initializes a new instance of <see cref="FileDialog"/>
  442. /// </summary>
  443. /// <param name="title">The title.</param>
  444. /// <param name="prompt">The prompt.</param>
  445. /// <param name="nameFieldLabel">The name field label.</param>
  446. /// <param name="message">The message.</param>
  447. public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null)
  448. {
  449. this.message = new Label (message) {
  450. X = 1,
  451. Y = 0,
  452. };
  453. Add (this.message);
  454. var msgLines = TextFormatter.MaxLines (message, Driver.Cols - 20);
  455. dirLabel = new Label ("Directory: ") {
  456. X = 1,
  457. Y = 1 + msgLines
  458. };
  459. dirEntry = new TextField ("") {
  460. X = Pos.Right (dirLabel),
  461. Y = 1 + msgLines,
  462. Width = Dim.Fill () - 1,
  463. };
  464. dirEntry.TextChanged += (e) => {
  465. DirectoryPath = dirEntry.Text;
  466. nameEntry.Text = ustring.Empty;
  467. };
  468. Add (dirLabel, dirEntry);
  469. this.nameFieldLabel = new Label ("Open: ") {
  470. X = 6,
  471. Y = 3 + msgLines,
  472. };
  473. nameEntry = new TextField ("") {
  474. X = Pos.Left (dirEntry),
  475. Y = 3 + msgLines,
  476. Width = Dim.Fill () - 1
  477. };
  478. Add (this.nameFieldLabel, nameEntry);
  479. dirListView = new DirListView (this) {
  480. X = 1,
  481. Y = 3 + msgLines + 2,
  482. Width = Dim.Fill () - 1,
  483. Height = Dim.Fill () - 2,
  484. };
  485. DirectoryPath = Path.GetFullPath (Environment.CurrentDirectory);
  486. Add (dirListView);
  487. dirListView.DirectoryChanged = (dir) => { nameEntry.Text = ustring.Empty; dirEntry.Text = dir; };
  488. dirListView.FileChanged = (file) => nameEntry.Text = file == ".." ? "" : file;
  489. dirListView.SelectedChanged = (file) => nameEntry.Text = file.Item1 == ".." ? "" : file.Item1;
  490. this.cancel = new Button ("Cancel");
  491. this.cancel.Clicked += () => {
  492. canceled = true;
  493. Application.RequestStop ();
  494. };
  495. AddButton (cancel);
  496. this.prompt = new Button (prompt) {
  497. IsDefault = true,
  498. };
  499. this.prompt.Clicked += () => {
  500. canceled = false;
  501. Application.RequestStop ();
  502. };
  503. AddButton (this.prompt);
  504. Width = Dim.Percent (80);
  505. Height = Dim.Percent (80);
  506. // On success, we will set this to false.
  507. canceled = true;
  508. }
  509. internal bool canceled;
  510. ///<inheritdoc/>
  511. public override void WillPresent ()
  512. {
  513. base.WillPresent ();
  514. //SetFocus (nameEntry);
  515. }
  516. //protected override void Dispose (bool disposing)
  517. //{
  518. // message?.Dispose ();
  519. // base.Dispose (disposing);
  520. //}
  521. /// <summary>
  522. /// Gets or sets the prompt label for the <see cref="Button"/> displayed to the user
  523. /// </summary>
  524. /// <value>The prompt.</value>
  525. public ustring Prompt {
  526. get => prompt.Text;
  527. set {
  528. prompt.Text = value;
  529. }
  530. }
  531. /// <summary>
  532. /// Gets or sets the name field label.
  533. /// </summary>
  534. /// <value>The name field label.</value>
  535. public ustring NameFieldLabel {
  536. get => nameFieldLabel.Text;
  537. set {
  538. nameFieldLabel.Text = value;
  539. }
  540. }
  541. /// <summary>
  542. /// Gets or sets the message displayed to the user, defaults to nothing
  543. /// </summary>
  544. /// <value>The message.</value>
  545. public ustring Message {
  546. get => message.Text;
  547. set {
  548. message.Text = value;
  549. }
  550. }
  551. /// <summary>
  552. /// Gets or sets a value indicating whether this <see cref="FileDialog"/> can create directories.
  553. /// </summary>
  554. /// <value><c>true</c> if can create directories; otherwise, <c>false</c>.</value>
  555. public bool CanCreateDirectories { get; set; }
  556. /// <summary>
  557. /// Gets or sets a value indicating whether this <see cref="FileDialog"/> is extension hidden.
  558. /// </summary>
  559. /// <value><c>true</c> if is extension hidden; otherwise, <c>false</c>.</value>
  560. public bool IsExtensionHidden { get; set; }
  561. /// <summary>
  562. /// Gets or sets the directory path for this panel
  563. /// </summary>
  564. /// <value>The directory path.</value>
  565. public ustring DirectoryPath {
  566. get => dirEntry.Text;
  567. set {
  568. dirEntry.Text = value;
  569. dirListView.Directory = value;
  570. }
  571. }
  572. /// <summary>
  573. /// The array of filename extensions allowed, or null if all file extensions are allowed.
  574. /// </summary>
  575. /// <value>The allowed file types.</value>
  576. public string [] AllowedFileTypes {
  577. get => dirListView.AllowedFileTypes;
  578. set => dirListView.AllowedFileTypes = value;
  579. }
  580. /// <summary>
  581. /// Gets or sets a value indicating whether this <see cref="FileDialog"/> allows the file to be saved with a different extension
  582. /// </summary>
  583. /// <value><c>true</c> if allows other file types; otherwise, <c>false</c>.</value>
  584. public bool AllowsOtherFileTypes { get; set; }
  585. /// <summary>
  586. /// The File path that is currently shown on the panel
  587. /// </summary>
  588. /// <value>The absolute file path for the file path entered.</value>
  589. public ustring FilePath {
  590. get => dirListView.MakePath (nameEntry.Text.ToString ());
  591. set {
  592. nameEntry.Text = Path.GetFileName (value.ToString ());
  593. }
  594. }
  595. /// <summary>
  596. /// Check if the dialog was or not canceled.
  597. /// </summary>
  598. public bool Canceled { get => canceled; }
  599. }
  600. /// <summary>
  601. /// The <see cref="SaveDialog"/> provides an interactive dialog box for users to pick a file to
  602. /// save.
  603. /// </summary>
  604. /// <remarks>
  605. /// <para>
  606. /// To use, create an instance of <see cref="SaveDialog"/>, and pass it to
  607. /// <see cref="Application.Run(Func{Exception, bool})"/>. This will run the dialog modally,
  608. /// and when this returns, the <see cref="FileName"/>property will contain the selected file name or
  609. /// null if the user canceled.
  610. /// </para>
  611. /// </remarks>
  612. public class SaveDialog : FileDialog {
  613. /// <summary>
  614. /// Initializes a new <see cref="SaveDialog"/>.
  615. /// </summary>
  616. public SaveDialog () : this (title: string.Empty, message: string.Empty) { }
  617. /// <summary>
  618. /// Initializes a new <see cref="SaveDialog"/>.
  619. /// </summary>
  620. /// <param name="title">The title.</param>
  621. /// <param name="message">The message.</param>
  622. public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message) { }
  623. /// <summary>
  624. /// Gets the name of the file the user selected for saving, or null
  625. /// if the user canceled the <see cref="SaveDialog"/>.
  626. /// </summary>
  627. /// <value>The name of the file.</value>
  628. public ustring FileName {
  629. get {
  630. if (canceled)
  631. return null;
  632. return Path.GetFileName (FilePath.ToString ());
  633. }
  634. }
  635. }
  636. /// <summary>
  637. /// The <see cref="OpenDialog"/>provides an interactive dialog box for users to select files or directories.
  638. /// </summary>
  639. /// <remarks>
  640. /// <para>
  641. /// The open dialog can be used to select files for opening, it can be configured to allow
  642. /// multiple items to be selected (based on the AllowsMultipleSelection) variable and
  643. /// you can control whether this should allow files or directories to be selected.
  644. /// </para>
  645. /// <para>
  646. /// To use, create an instance of <see cref="OpenDialog"/>, and pass it to
  647. /// <see cref="Application.Run(Func{Exception, bool})"/>. This will run the dialog modally,
  648. /// and when this returns, the list of filds will be available on the <see cref="FilePaths"/> property.
  649. /// </para>
  650. /// <para>
  651. /// To select more than one file, users can use the spacebar, or control-t.
  652. /// </para>
  653. /// </remarks>
  654. public class OpenDialog : FileDialog {
  655. /// <summary>
  656. /// Initializes a new <see cref="OpenDialog"/>.
  657. /// </summary>
  658. public OpenDialog () : this (title: string.Empty, message: string.Empty) { }
  659. /// <summary>
  660. /// Initializes a new <see cref="OpenDialog"/>.
  661. /// </summary>
  662. /// <param name="title"></param>
  663. /// <param name="message"></param>
  664. public OpenDialog (ustring title, ustring message) : base (title, prompt: "Open", nameFieldLabel: "Open", message: message)
  665. {
  666. }
  667. /// <summary>
  668. /// Gets or sets a value indicating whether this <see cref="Terminal.Gui.OpenDialog"/> can choose files.
  669. /// </summary>
  670. /// <value><c>true</c> if can choose files; otherwise, <c>false</c>. Defaults to <c>true</c></value>
  671. public bool CanChooseFiles {
  672. get => dirListView.canChooseFiles;
  673. set {
  674. dirListView.canChooseFiles = value;
  675. dirListView.Reload ();
  676. }
  677. }
  678. /// <summary>
  679. /// Gets or sets a value indicating whether this <see cref="OpenDialog"/> can choose directories.
  680. /// </summary>
  681. /// <value><c>true</c> if can choose directories; otherwise, <c>false</c> defaults to <c>false</c>.</value>
  682. public bool CanChooseDirectories {
  683. get => dirListView.canChooseDirectories;
  684. set {
  685. dirListView.canChooseDirectories = value;
  686. dirListView.Reload ();
  687. }
  688. }
  689. /// <summary>
  690. /// Gets or sets a value indicating whether this <see cref="OpenDialog"/> allows multiple selection.
  691. /// </summary>
  692. /// <value><c>true</c> if allows multiple selection; otherwise, <c>false</c>, defaults to false.</value>
  693. public bool AllowsMultipleSelection {
  694. get => dirListView.allowsMultipleSelection;
  695. set {
  696. dirListView.allowsMultipleSelection = value;
  697. dirListView.Reload ();
  698. }
  699. }
  700. /// <summary>
  701. /// Returns the selected files, or an empty list if nothing has been selected
  702. /// </summary>
  703. /// <value>The file paths.</value>
  704. public IReadOnlyList<string> FilePaths {
  705. get => dirListView.FilePaths;
  706. }
  707. }
  708. }