TreeViewFileSystem.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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. _detailsFrame = new DetailsFrame (_iconProvider)
  179. {
  180. X = Pos.Right (_treeViewFiles), Y = 0, Width = Dim.Fill (), Height = Dim.Fill ()
  181. };
  182. win.Add (_detailsFrame);
  183. _treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
  184. _treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
  185. _treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
  186. SetupFileTree ();
  187. win.Add (_treeViewFiles);
  188. top.Add (win);
  189. _treeViewFiles.GoToFirst ();
  190. _treeViewFiles.Expand ();
  191. SetupScrollBar ();
  192. _treeViewFiles.SetFocus ();
  193. UpdateIconCheckedness ();
  194. Application.Run (top);
  195. top.Dispose ();
  196. Application.Shutdown ();
  197. }
  198. private string AspectGetter (IFileSystemInfo f) { return (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim (); }
  199. private void InvertExpandableSymbols ()
  200. {
  201. _miInvertSymbols.Checked = !_miInvertSymbols.Checked;
  202. _treeViewFiles.Style.InvertExpandSymbolColors = (bool)_miInvertSymbols.Checked;
  203. _treeViewFiles.SetNeedsDisplay ();
  204. }
  205. private void Quit () { Application.RequestStop (); }
  206. private void SetCheckHighlightModelTextOnly ()
  207. {
  208. _treeViewFiles.Style.HighlightModelTextOnly = !_treeViewFiles.Style.HighlightModelTextOnly;
  209. _miHighlightModelTextOnly.Checked = _treeViewFiles.Style.HighlightModelTextOnly;
  210. _treeViewFiles.SetNeedsDisplay ();
  211. }
  212. private void SetCursor ()
  213. {
  214. _miCursor.Checked = !_miCursor.Checked;
  215. _treeViewFiles.CursorVisibility =
  216. _miCursor.Checked == true ? CursorVisibility.Default : CursorVisibility.Invisible;
  217. }
  218. private void SetCustomColors ()
  219. {
  220. _miCustomColors.Checked = !_miCustomColors.Checked;
  221. if (_miCustomColors.Checked == true)
  222. {
  223. _treeViewFiles.ColorGetter = m =>
  224. {
  225. if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
  226. {
  227. return new ColorScheme
  228. {
  229. Focus = new Attribute (
  230. Color.BrightRed,
  231. _treeViewFiles.ColorScheme.Focus.Background
  232. ),
  233. Normal = new Attribute (
  234. Color.BrightYellow,
  235. _treeViewFiles.ColorScheme.Normal.Background
  236. )
  237. };
  238. ;
  239. }
  240. if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
  241. {
  242. return new ColorScheme
  243. {
  244. Focus = new Attribute (
  245. Color.BrightRed,
  246. _treeViewFiles.ColorScheme.Focus.Background
  247. ),
  248. Normal = new Attribute (
  249. Color.BrightYellow,
  250. _treeViewFiles.ColorScheme.Normal.Background
  251. )
  252. };
  253. ;
  254. }
  255. return null;
  256. };
  257. }
  258. else
  259. {
  260. _treeViewFiles.ColorGetter = null;
  261. }
  262. _treeViewFiles.SetNeedsDisplay ();
  263. }
  264. private void SetExpandableSymbols (Rune expand, Rune? collapse)
  265. {
  266. _miPlusMinus.Checked = expand.Value == '+';
  267. _miArrowSymbols.Checked = expand.Value == '>';
  268. _miNoSymbols.Checked = expand.Value == default (int);
  269. _treeViewFiles.Style.ExpandableSymbol = expand;
  270. _treeViewFiles.Style.CollapseableSymbol = collapse;
  271. _treeViewFiles.SetNeedsDisplay ();
  272. }
  273. private void SetFullName ()
  274. {
  275. _miFullPaths.Checked = !_miFullPaths.Checked;
  276. if (_miFullPaths.Checked == true)
  277. {
  278. _treeViewFiles.AspectGetter = f => f.FullName;
  279. }
  280. else
  281. {
  282. _treeViewFiles.AspectGetter = f => f.Name;
  283. }
  284. _treeViewFiles.SetNeedsDisplay ();
  285. }
  286. private void SetLeaveLastRow ()
  287. {
  288. _miLeaveLastRow.Checked = !_miLeaveLastRow.Checked;
  289. _treeViewFiles.Style.LeaveLastRow = (bool)_miLeaveLastRow.Checked;
  290. }
  291. private void SetMultiSelect ()
  292. {
  293. _miMultiSelect.Checked = !_miMultiSelect.Checked;
  294. _treeViewFiles.MultiSelect = (bool)_miMultiSelect.Checked;
  295. }
  296. private void SetNerdIcons ()
  297. {
  298. _iconProvider.UseNerdIcons = true;
  299. UpdateIconCheckedness ();
  300. }
  301. private void SetNoIcons ()
  302. {
  303. _iconProvider.UseUnicodeCharacters = false;
  304. _iconProvider.UseNerdIcons = false;
  305. UpdateIconCheckedness ();
  306. }
  307. private void SetUnicodeIcons ()
  308. {
  309. _iconProvider.UseUnicodeCharacters = true;
  310. UpdateIconCheckedness ();
  311. }
  312. private void SetupFileTree ()
  313. {
  314. // setup how to build tree
  315. var fs = new FileSystem ();
  316. IEnumerable<IDirectoryInfo> rootDirs =
  317. DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
  318. _treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
  319. _treeViewFiles.AddObjects (rootDirs);
  320. // Determines how to represent objects as strings on the screen
  321. _treeViewFiles.AspectGetter = AspectGetter;
  322. _iconProvider.IsOpenGetter = _treeViewFiles.IsExpanded;
  323. }
  324. private void SetupScrollBar ()
  325. {
  326. // When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
  327. _treeViewFiles.Style.LeaveLastRow = true;
  328. var scrollBar = new ScrollBarView (_treeViewFiles, true);
  329. scrollBar.ChangedPosition += (s, e) =>
  330. {
  331. _treeViewFiles.ScrollOffsetVertical = scrollBar.Position;
  332. if (_treeViewFiles.ScrollOffsetVertical != scrollBar.Position)
  333. {
  334. scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
  335. }
  336. _treeViewFiles.SetNeedsDisplay ();
  337. };
  338. scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
  339. {
  340. _treeViewFiles.ScrollOffsetHorizontal = scrollBar.OtherScrollBarView.Position;
  341. if (_treeViewFiles.ScrollOffsetHorizontal != scrollBar.OtherScrollBarView.Position)
  342. {
  343. scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
  344. }
  345. _treeViewFiles.SetNeedsDisplay ();
  346. };
  347. _treeViewFiles.DrawingContent += (s, e) =>
  348. {
  349. scrollBar.Size = _treeViewFiles.ContentHeight;
  350. scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
  351. scrollBar.OtherScrollBarView.Size = _treeViewFiles.GetContentWidth (true);
  352. scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
  353. scrollBar.Refresh ();
  354. };
  355. }
  356. private void ShowColoredExpandableSymbols ()
  357. {
  358. _miColoredSymbols.Checked = !_miColoredSymbols.Checked;
  359. _treeViewFiles.Style.ColorExpandSymbol = (bool)_miColoredSymbols.Checked;
  360. _treeViewFiles.SetNeedsDisplay ();
  361. }
  362. private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
  363. {
  364. var menu = new ContextMenu { Position = screenPoint };
  365. var menuItems = new MenuBarItem (
  366. new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) }
  367. );
  368. Application.Invoke (() => menu.Show (menuItems));
  369. }
  370. private void ShowLines ()
  371. {
  372. _miShowLines.Checked = !_miShowLines.Checked;
  373. _treeViewFiles.Style.ShowBranchLines = (bool)_miShowLines.Checked!;
  374. _treeViewFiles.SetNeedsDisplay ();
  375. }
  376. private void ShowPropertiesOf (IFileSystemInfo fileSystemInfo) { _detailsFrame.FileInfo = fileSystemInfo; }
  377. private void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
  378. {
  379. // Render directory icons in yellow
  380. if (e.Model is IDirectoryInfo d)
  381. {
  382. if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters)
  383. {
  384. if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.Cells.Count)
  385. {
  386. Cell cell = e.Cells [e.IndexOfModelText];
  387. cell.Attribute = new Attribute (
  388. Color.BrightYellow,
  389. cell.Attribute!.Value.Background
  390. );
  391. }
  392. }
  393. }
  394. }
  395. private void TreeViewFiles_KeyPress (object sender, Key obj)
  396. {
  397. if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask))
  398. {
  399. IFileSystemInfo selected = _treeViewFiles.SelectedObject;
  400. // nothing is selected
  401. if (selected == null)
  402. {
  403. return;
  404. }
  405. int? location = _treeViewFiles.GetObjectRow (selected);
  406. //selected object is offscreen or somehow not found
  407. if (location == null || location < 0 || location > _treeViewFiles.Frame.Height)
  408. {
  409. return;
  410. }
  411. ShowContextMenu (
  412. new Point (
  413. 5 + _treeViewFiles.Frame.X,
  414. location.Value + _treeViewFiles.Frame.Y + 2
  415. ),
  416. selected
  417. );
  418. }
  419. }
  420. private void TreeViewFiles_MouseClick (object sender, MouseEventArgs obj)
  421. {
  422. // if user right clicks
  423. if (obj.Flags.HasFlag (MouseFlags.Button3Clicked))
  424. {
  425. IFileSystemInfo rightClicked = _treeViewFiles.GetObjectOnRow (obj.Position.Y);
  426. // nothing was clicked
  427. if (rightClicked == null)
  428. {
  429. return;
  430. }
  431. ShowContextMenu (
  432. new Point (
  433. obj.Position.X + _treeViewFiles.Frame.X,
  434. obj.Position.Y + _treeViewFiles.Frame.Y + 2
  435. ),
  436. rightClicked
  437. );
  438. }
  439. }
  440. private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e) { ShowPropertiesOf (e.NewValue); }
  441. private void UpdateIconCheckedness ()
  442. {
  443. _miBasicIcons.Checked = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters;
  444. _miUnicodeIcons.Checked = _iconProvider.UseUnicodeCharacters;
  445. _miNerdIcons.Checked = _iconProvider.UseNerdIcons;
  446. _treeViewFiles.SetNeedsDisplay ();
  447. }
  448. private class DetailsFrame : FrameView
  449. {
  450. private readonly FileSystemIconProvider _iconProvider;
  451. private IFileSystemInfo _fileInfo;
  452. public DetailsFrame (FileSystemIconProvider iconProvider)
  453. {
  454. Title = "Details";
  455. Visible = true;
  456. CanFocus = true;
  457. _iconProvider = iconProvider;
  458. }
  459. public IFileSystemInfo FileInfo
  460. {
  461. get => _fileInfo;
  462. set
  463. {
  464. _fileInfo = value;
  465. StringBuilder sb = null;
  466. if (_fileInfo is IFileInfo f)
  467. {
  468. Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
  469. sb = new StringBuilder ();
  470. sb.AppendLine ($"Path:\n {f.FullName}\n");
  471. sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
  472. sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
  473. sb.AppendLine ($"Created:\n {f.CreationTime}");
  474. }
  475. if (_fileInfo is IDirectoryInfo dir)
  476. {
  477. Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
  478. sb = new StringBuilder ();
  479. sb.AppendLine ($"Path:\n {dir?.FullName}\n");
  480. sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
  481. sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
  482. }
  483. Text = sb.ToString ();
  484. }
  485. }
  486. }
  487. }