TreeViewFileSystem.cs 24 KB

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