TreeViewFileSystem.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. using System.IO.Abstractions;
  2. using System.Text;
  3. using Terminal.Gui;
  4. namespace UICatalog.Scenarios;
  5. [ScenarioMetadata ("File System Explorer", "Hierarchical file system explorer demonstrating TreeView.")]
  6. [ScenarioCategory ("Controls")]
  7. [ScenarioCategory ("TreeView")]
  8. [ScenarioCategory ("Files and IO")]
  9. public class TreeViewFileSystem : Scenario
  10. {
  11. private readonly FileSystemIconProvider _iconProvider = new ();
  12. private DetailsFrame _detailsFrame;
  13. private MenuItem _miArrowSymbols;
  14. private MenuItem _miBasicIcons;
  15. private MenuItem _miColoredSymbols;
  16. private MenuItem _miCursor;
  17. private MenuItem _miCustomColors;
  18. private MenuItem _miFullPaths;
  19. private MenuItem _miHighlightModelTextOnly;
  20. private MenuItem _miInvertSymbols;
  21. private MenuItem _miLeaveLastRow;
  22. private MenuItem _miMultiSelect;
  23. private MenuItem _miNerdIcons;
  24. private MenuItem _miNoSymbols;
  25. private MenuItem _miPlusMinus;
  26. private MenuItem _miShowLines;
  27. private MenuItem _miUnicodeIcons;
  28. /// <summary>A tree view where nodes are files and folders</summary>
  29. private TreeView<IFileSystemInfo> _treeViewFiles;
  30. public override void Main ()
  31. {
  32. Application.Init ();
  33. var win = new Window
  34. {
  35. Title = GetName (),
  36. Y = 1, // menu
  37. Height = Dim.Fill ()
  38. };
  39. var top = new Toplevel ();
  40. var menu = new MenuBar
  41. {
  42. Menus =
  43. [
  44. new (
  45. "_File",
  46. new MenuItem []
  47. {
  48. new (
  49. "_Quit",
  50. $"{Application.QuitKey}",
  51. () => Quit ()
  52. )
  53. }
  54. ),
  55. new (
  56. "_View",
  57. new []
  58. {
  59. _miFullPaths =
  60. new ("_Full Paths", "", () => SetFullName ())
  61. {
  62. Checked = false, CheckType = MenuItemCheckStyle.Checked
  63. },
  64. _miMultiSelect = new (
  65. "_Multi Select",
  66. "",
  67. () => SetMultiSelect ()
  68. )
  69. {
  70. Checked = true,
  71. CheckType = MenuItemCheckStyle
  72. .Checked
  73. }
  74. }
  75. ),
  76. new (
  77. "_Style",
  78. new []
  79. {
  80. _miShowLines =
  81. new ("_Show Lines", "", () => ShowLines ())
  82. {
  83. Checked = true, CheckType = MenuItemCheckStyle.Checked
  84. },
  85. null /*separator*/,
  86. _miPlusMinus =
  87. new (
  88. "_Plus Minus Symbols",
  89. "+ -",
  90. () => SetExpandableSymbols (
  91. (Rune)'+',
  92. (Rune)'-'
  93. )
  94. ) { Checked = true, CheckType = MenuItemCheckStyle.Radio },
  95. _miArrowSymbols =
  96. new (
  97. "_Arrow Symbols",
  98. "> v",
  99. () => SetExpandableSymbols (
  100. (Rune)'>',
  101. (Rune)'v'
  102. )
  103. ) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
  104. _miNoSymbols =
  105. new (
  106. "_No Symbols",
  107. "",
  108. () => SetExpandableSymbols (
  109. default (Rune),
  110. null
  111. )
  112. ) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
  113. null /*separator*/,
  114. _miColoredSymbols =
  115. new (
  116. "_Colored Symbols",
  117. "",
  118. () => ShowColoredExpandableSymbols ()
  119. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  120. _miInvertSymbols =
  121. new (
  122. "_Invert Symbols",
  123. "",
  124. () => InvertExpandableSymbols ()
  125. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  126. null /*separator*/,
  127. _miBasicIcons =
  128. new ("_Basic Icons", null, SetNoIcons)
  129. {
  130. Checked = false, CheckType = MenuItemCheckStyle.Radio
  131. },
  132. _miUnicodeIcons =
  133. new ("_Unicode Icons", null, SetUnicodeIcons)
  134. {
  135. Checked = false, CheckType = MenuItemCheckStyle.Radio
  136. },
  137. _miNerdIcons =
  138. new ("_Nerd Icons", null, SetNerdIcons)
  139. {
  140. Checked = false, CheckType = MenuItemCheckStyle.Radio
  141. },
  142. null /*separator*/,
  143. _miLeaveLastRow =
  144. new (
  145. "_Leave Last Row",
  146. "",
  147. () => SetLeaveLastRow ()
  148. ) { Checked = true, CheckType = MenuItemCheckStyle.Checked },
  149. _miHighlightModelTextOnly =
  150. new (
  151. "_Highlight Model Text Only",
  152. "",
  153. () => SetCheckHighlightModelTextOnly ()
  154. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  155. null /*separator*/,
  156. _miCustomColors =
  157. new (
  158. "C_ustom Colors Hidden Files",
  159. "Yellow/Red",
  160. () => SetCustomColors ()
  161. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  162. null /*separator*/,
  163. _miCursor = new (
  164. "Curs_or (MultiSelect only)",
  165. "",
  166. () => SetCursor ()
  167. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }
  168. }
  169. )
  170. ]
  171. };
  172. top.Add (menu);
  173. _treeViewFiles = new () { X = 0, Y = 0, Width = Dim.Percent (50), Height = Dim.Fill () };
  174. _treeViewFiles.DrawLine += TreeViewFiles_DrawLine;
  175. _treeViewFiles.VerticalScrollBar.AutoShow = false;
  176. _detailsFrame = new (_iconProvider)
  177. {
  178. X = Pos.Right (_treeViewFiles), Y = 0, Width = Dim.Fill (), Height = Dim.Fill ()
  179. };
  180. win.Add (_detailsFrame);
  181. _treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
  182. _treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
  183. _treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
  184. SetupFileTree ();
  185. win.Add (_treeViewFiles);
  186. top.Add (win);
  187. _treeViewFiles.GoToFirst ();
  188. _treeViewFiles.Expand ();
  189. //SetupScrollBar ();
  190. _treeViewFiles.SetFocus ();
  191. UpdateIconCheckedness ();
  192. Application.Run (top);
  193. top.Dispose ();
  194. Application.Shutdown ();
  195. }
  196. private string AspectGetter (IFileSystemInfo f) { return (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim (); }
  197. private void InvertExpandableSymbols ()
  198. {
  199. _miInvertSymbols.Checked = !_miInvertSymbols.Checked;
  200. _treeViewFiles.Style.InvertExpandSymbolColors = (bool)_miInvertSymbols.Checked;
  201. _treeViewFiles.SetNeedsDraw ();
  202. }
  203. private void Quit () { Application.RequestStop (); }
  204. private void SetCheckHighlightModelTextOnly ()
  205. {
  206. _treeViewFiles.Style.HighlightModelTextOnly = !_treeViewFiles.Style.HighlightModelTextOnly;
  207. _miHighlightModelTextOnly.Checked = _treeViewFiles.Style.HighlightModelTextOnly;
  208. _treeViewFiles.SetNeedsDraw ();
  209. }
  210. private void SetCursor ()
  211. {
  212. _miCursor.Checked = !_miCursor.Checked;
  213. _treeViewFiles.CursorVisibility =
  214. _miCursor.Checked == true ? CursorVisibility.Default : CursorVisibility.Invisible;
  215. }
  216. private void SetCustomColors ()
  217. {
  218. _miCustomColors.Checked = !_miCustomColors.Checked;
  219. if (_miCustomColors.Checked == true)
  220. {
  221. _treeViewFiles.ColorGetter = m =>
  222. {
  223. if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
  224. {
  225. return new ()
  226. {
  227. Focus = new (
  228. Color.BrightRed,
  229. _treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
  230. ),
  231. Normal = new (
  232. Color.BrightYellow,
  233. _treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
  234. )
  235. };
  236. ;
  237. }
  238. if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
  239. {
  240. return new ()
  241. {
  242. Focus = new (
  243. Color.BrightRed,
  244. _treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
  245. ),
  246. Normal = new (
  247. Color.BrightYellow,
  248. _treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
  249. )
  250. };
  251. ;
  252. }
  253. return null;
  254. };
  255. }
  256. else
  257. {
  258. _treeViewFiles.ColorGetter = null;
  259. }
  260. _treeViewFiles.SetNeedsDraw ();
  261. }
  262. private void SetExpandableSymbols (Rune expand, Rune? collapse)
  263. {
  264. _miPlusMinus.Checked = expand.Value == '+';
  265. _miArrowSymbols.Checked = expand.Value == '>';
  266. _miNoSymbols.Checked = expand.Value == default (int);
  267. _treeViewFiles.Style.ExpandableSymbol = expand;
  268. _treeViewFiles.Style.CollapseableSymbol = collapse;
  269. _treeViewFiles.SetNeedsDraw ();
  270. }
  271. private void SetFullName ()
  272. {
  273. _miFullPaths.Checked = !_miFullPaths.Checked;
  274. if (_miFullPaths.Checked == true)
  275. {
  276. _treeViewFiles.AspectGetter = f => f.FullName;
  277. }
  278. else
  279. {
  280. _treeViewFiles.AspectGetter = f => f.Name;
  281. }
  282. _treeViewFiles.SetNeedsDraw ();
  283. }
  284. private void SetLeaveLastRow ()
  285. {
  286. _miLeaveLastRow.Checked = !_miLeaveLastRow.Checked;
  287. _treeViewFiles.Style.LeaveLastRow = (bool)_miLeaveLastRow.Checked;
  288. }
  289. private void SetMultiSelect ()
  290. {
  291. _miMultiSelect.Checked = !_miMultiSelect.Checked;
  292. _treeViewFiles.MultiSelect = (bool)_miMultiSelect.Checked;
  293. }
  294. private void SetNerdIcons ()
  295. {
  296. _iconProvider.UseNerdIcons = true;
  297. UpdateIconCheckedness ();
  298. }
  299. private void SetNoIcons ()
  300. {
  301. _iconProvider.UseUnicodeCharacters = false;
  302. _iconProvider.UseNerdIcons = false;
  303. UpdateIconCheckedness ();
  304. }
  305. private void SetUnicodeIcons ()
  306. {
  307. _iconProvider.UseUnicodeCharacters = true;
  308. UpdateIconCheckedness ();
  309. }
  310. private void SetupFileTree ()
  311. {
  312. // setup how to build tree
  313. var fs = new FileSystem ();
  314. IEnumerable<IDirectoryInfo> rootDirs =
  315. DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
  316. _treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
  317. _treeViewFiles.AddObjects (rootDirs);
  318. // Determines how to represent objects as strings on the screen
  319. _treeViewFiles.AspectGetter = AspectGetter;
  320. _iconProvider.IsOpenGetter = _treeViewFiles.IsExpanded;
  321. }
  322. //private void SetupScrollBar ()
  323. //{
  324. // // When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
  325. // _treeViewFiles.Style.LeaveLastRow = true;
  326. // var scrollBar = new ScrollBarView (_treeViewFiles, true);
  327. // scrollBar.ChangedPosition += (s, e) =>
  328. // {
  329. // _treeViewFiles.ScrollOffsetVertical = scrollBar.Position;
  330. // if (_treeViewFiles.ScrollOffsetVertical != scrollBar.Position)
  331. // {
  332. // scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
  333. // }
  334. // _treeViewFiles.SetNeedsDraw ();
  335. // };
  336. // scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
  337. // {
  338. // _treeViewFiles.ScrollOffsetHorizontal = scrollBar.OtherScrollBarView.Position;
  339. // if (_treeViewFiles.ScrollOffsetHorizontal != scrollBar.OtherScrollBarView.Position)
  340. // {
  341. // scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
  342. // }
  343. // _treeViewFiles.SetNeedsDraw ();
  344. // };
  345. // _treeViewFiles.DrawingContent += (s, e) =>
  346. // {
  347. // scrollBar.Size = _treeViewFiles.ContentHeight;
  348. // scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
  349. // scrollBar.OtherScrollBarView.Size = _treeViewFiles.GetContentWidth (true);
  350. // scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
  351. // scrollBar.Refresh ();
  352. // };
  353. //}
  354. private void ShowColoredExpandableSymbols ()
  355. {
  356. _miColoredSymbols.Checked = !_miColoredSymbols.Checked;
  357. _treeViewFiles.Style.ColorExpandSymbol = (bool)_miColoredSymbols.Checked;
  358. _treeViewFiles.SetNeedsDraw ();
  359. }
  360. private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
  361. {
  362. PopoverMenu? contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
  363. // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
  364. // and the context menu is disposed when it is closed.
  365. Application.Popover?.Register (contextMenu);
  366. Application.Invoke (() => contextMenu?.MakeVisible (screenPoint));
  367. }
  368. private void ShowLines ()
  369. {
  370. _miShowLines.Checked = !_miShowLines.Checked;
  371. _treeViewFiles.Style.ShowBranchLines = (bool)_miShowLines.Checked!;
  372. _treeViewFiles.SetNeedsDraw ();
  373. }
  374. private void ShowPropertiesOf (IFileSystemInfo fileSystemInfo) { _detailsFrame.FileInfo = fileSystemInfo; }
  375. private void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
  376. {
  377. // Render directory icons in yellow
  378. if (e.Model is IDirectoryInfo d)
  379. {
  380. if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters)
  381. {
  382. if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.Cells.Count)
  383. {
  384. Cell cell = e.Cells [e.IndexOfModelText];
  385. cell.Attribute = new Attribute (
  386. Color.BrightYellow,
  387. cell.Attribute!.Value.Background,
  388. cell.Attribute!.Value.Style
  389. );
  390. }
  391. }
  392. }
  393. }
  394. private void TreeViewFiles_KeyPress (object sender, Key obj)
  395. {
  396. if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask))
  397. {
  398. IFileSystemInfo selected = _treeViewFiles.SelectedObject;
  399. // nothing is selected
  400. if (selected == null)
  401. {
  402. return;
  403. }
  404. int? location = _treeViewFiles.GetObjectRow (selected);
  405. //selected object is offscreen or somehow not found
  406. if (location == null || location < 0 || location > _treeViewFiles.Frame.Height)
  407. {
  408. return;
  409. }
  410. ShowContextMenu (
  411. new (
  412. 5 + _treeViewFiles.Frame.X,
  413. location.Value + _treeViewFiles.Frame.Y + 2
  414. ),
  415. selected
  416. );
  417. }
  418. }
  419. private void TreeViewFiles_MouseClick (object sender, MouseEventArgs obj)
  420. {
  421. // if user right clicks
  422. if (obj.Flags.HasFlag (MouseFlags.Button3Clicked))
  423. {
  424. IFileSystemInfo rightClicked = _treeViewFiles.GetObjectOnRow (obj.Position.Y);
  425. // nothing was clicked
  426. if (rightClicked == null)
  427. {
  428. return;
  429. }
  430. ShowContextMenu (
  431. new (
  432. obj.Position.X + _treeViewFiles.Frame.X,
  433. obj.Position.Y + _treeViewFiles.Frame.Y + 2
  434. ),
  435. rightClicked
  436. );
  437. }
  438. }
  439. private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e) { ShowPropertiesOf (e.NewValue); }
  440. private void UpdateIconCheckedness ()
  441. {
  442. _miBasicIcons.Checked = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters;
  443. _miUnicodeIcons.Checked = _iconProvider.UseUnicodeCharacters;
  444. _miNerdIcons.Checked = _iconProvider.UseNerdIcons;
  445. _treeViewFiles.SetNeedsDraw ();
  446. }
  447. private class DetailsFrame : FrameView
  448. {
  449. private readonly FileSystemIconProvider _iconProvider;
  450. private IFileSystemInfo _fileInfo;
  451. public DetailsFrame (FileSystemIconProvider iconProvider)
  452. {
  453. Title = "Details";
  454. Visible = true;
  455. CanFocus = true;
  456. _iconProvider = iconProvider;
  457. }
  458. public IFileSystemInfo FileInfo
  459. {
  460. get => _fileInfo;
  461. set
  462. {
  463. _fileInfo = value;
  464. StringBuilder sb = null;
  465. if (_fileInfo is IFileInfo f)
  466. {
  467. Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
  468. sb = new ();
  469. sb.AppendLine ($"Path:\n {f.FullName}\n");
  470. sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
  471. sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
  472. sb.AppendLine ($"Created:\n {f.CreationTime}");
  473. }
  474. if (_fileInfo is IDirectoryInfo dir)
  475. {
  476. Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
  477. sb = new ();
  478. sb.AppendLine ($"Path:\n {dir?.FullName}\n");
  479. sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
  480. sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
  481. }
  482. Text = sb.ToString ();
  483. }
  484. }
  485. }
  486. }