TreeViewFileSystem.cs 24 KB

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