TreeViewFileSystem.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection.PortableExecutable;
  6. using Terminal.Gui;
  7. using Terminal.Gui.Trees;
  8. namespace UICatalog.Scenarios {
  9. [ScenarioMetadata (Name: "File System Explorer", Description: "Hierarchical file system explorer demonstrating TreeView.")]
  10. [ScenarioCategory ("Controls"), ScenarioCategory ("TreeView"), ScenarioCategory ("Files and IO")]
  11. public class TreeViewFileSystem : Scenario {
  12. /// <summary>
  13. /// A tree view where nodes are files and folders
  14. /// </summary>
  15. TreeView<FileSystemInfo> treeViewFiles;
  16. MenuItem miShowLines;
  17. private MenuItem miPlusMinus;
  18. private MenuItem miArrowSymbols;
  19. private MenuItem miNoSymbols;
  20. private MenuItem miColoredSymbols;
  21. private MenuItem miInvertSymbols;
  22. private MenuItem miUnicodeSymbols;
  23. private MenuItem miFullPaths;
  24. private MenuItem miLeaveLastRow;
  25. private MenuItem miHighlightModelTextOnly;
  26. private MenuItem miCustomColors;
  27. private MenuItem miCursor;
  28. private MenuItem miMultiSelect;
  29. private DetailsFrame detailsFrame;
  30. public override void Setup ()
  31. {
  32. Win.Title = this.GetName ();
  33. Win.Y = 1; // menu
  34. Win.Height = Dim.Fill ();
  35. Application.Top.LayoutSubviews ();
  36. var menu = new MenuBar (new MenuBarItem [] {
  37. new MenuBarItem ("_File", new MenuItem [] {
  38. new MenuItem ("_Quit", "CTRL-Q", () => Quit()),
  39. }),
  40. new MenuBarItem ("_View", new MenuItem [] {
  41. miFullPaths = new MenuItem ("_Full Paths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
  42. miMultiSelect = new MenuItem ("_Multi Select", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
  43. }),
  44. new MenuBarItem ("_Style", new MenuItem [] {
  45. miShowLines = new MenuItem ("_Show Lines", "", () => ShowLines()){
  46. Checked = true, CheckType = MenuItemCheckStyle.Checked
  47. },
  48. null /*separator*/,
  49. miPlusMinus = new MenuItem ("_Plus Minus Symbols", "+ -", () => SetExpandableSymbols('+','-')){Checked = true, CheckType = MenuItemCheckStyle.Radio},
  50. miArrowSymbols = new MenuItem ("_Arrow Symbols", "> v", () => SetExpandableSymbols('>','v')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
  51. miNoSymbols = new MenuItem ("_No Symbols", "", () => SetExpandableSymbols(null,null)){Checked = false, CheckType = MenuItemCheckStyle.Radio},
  52. miUnicodeSymbols = new MenuItem ("_Unicode", "ஹ ﷽", () => SetExpandableSymbols('ஹ','﷽')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
  53. null /*separator*/,
  54. miColoredSymbols = new MenuItem ("_Colored Symbols", "", () => ShowColoredExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
  55. miInvertSymbols = new MenuItem ("_Invert Symbols", "", () => InvertExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
  56. null /*separator*/,
  57. miLeaveLastRow = new MenuItem ("_Leave Last Row", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
  58. miHighlightModelTextOnly = new MenuItem ("_Highlight Model Text Only", "", () => SetCheckHighlightModelTextOnly()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
  59. null /*separator*/,
  60. miCustomColors = new MenuItem ("C_ustom Colors Hidden Files", "Yellow/Red", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
  61. null /*separator*/,
  62. miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
  63. }),
  64. });
  65. Application.Top.Add (menu);
  66. treeViewFiles = new TreeView<FileSystemInfo> () {
  67. X = 0,
  68. Y = 0,
  69. Width = Dim.Percent (50),
  70. Height = Dim.Fill (),
  71. };
  72. detailsFrame = new DetailsFrame () {
  73. X = Pos.Right (treeViewFiles) + 1,
  74. Y = 0,
  75. Width = Dim.Fill (),
  76. Height = Dim.Fill (),
  77. };
  78. Win.Add (detailsFrame);
  79. treeViewFiles.ObjectActivated += TreeViewFiles_ObjectActivated;
  80. treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
  81. treeViewFiles.KeyPress += TreeViewFiles_KeyPress;
  82. treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
  83. SetupFileTree ();
  84. Win.Add (treeViewFiles);
  85. treeViewFiles.GoToFirst ();
  86. treeViewFiles.Expand ();
  87. SetupScrollBar ();
  88. treeViewFiles.SetFocus ();
  89. }
  90. private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<FileSystemInfo> e)
  91. {
  92. ShowPropertiesOf (e.NewValue);
  93. }
  94. private void TreeViewFiles_KeyPress (View.KeyEventEventArgs obj)
  95. {
  96. if (obj.KeyEvent.Key == (Key.R | Key.CtrlMask)) {
  97. var selected = treeViewFiles.SelectedObject;
  98. // nothing is selected
  99. if (selected == null)
  100. return;
  101. var location = treeViewFiles.GetObjectRow (selected);
  102. //selected object is offscreen or somehow not found
  103. if (location == null || location < 0 || location > treeViewFiles.Frame.Height)
  104. return;
  105. ShowContextMenu (new Point (
  106. 5 + treeViewFiles.Frame.X,
  107. location.Value + treeViewFiles.Frame.Y + 2),
  108. selected);
  109. }
  110. }
  111. private void TreeViewFiles_MouseClick (View.MouseEventArgs obj)
  112. {
  113. // if user right clicks
  114. if (obj.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
  115. var rightClicked = treeViewFiles.GetObjectOnRow (obj.MouseEvent.Y);
  116. // nothing was clicked
  117. if (rightClicked == null)
  118. return;
  119. ShowContextMenu (new Point (
  120. obj.MouseEvent.X + treeViewFiles.Frame.X,
  121. obj.MouseEvent.Y + treeViewFiles.Frame.Y + 2),
  122. rightClicked);
  123. }
  124. }
  125. private void ShowContextMenu (Point screenPoint, FileSystemInfo forObject)
  126. {
  127. var menu = new ContextMenu ();
  128. menu.Position = screenPoint;
  129. menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) });
  130. Application.MainLoop.Invoke (menu.Show);
  131. }
  132. class DetailsFrame : FrameView {
  133. private FileSystemInfo fileInfo;
  134. TextView details = new TextView () {
  135. X = 1,
  136. Y = 0,
  137. Width = Dim.Fill (),
  138. Height = Dim.Fill (),
  139. ColorScheme = Colors.Base,
  140. WordWrap = true,
  141. ReadOnly = true
  142. };
  143. public DetailsFrame ()
  144. {
  145. Title = "Details";
  146. ColorScheme = Colors.Dialog;
  147. Visible = true;
  148. CanFocus = true;
  149. Add (details);
  150. }
  151. public FileSystemInfo FileInfo {
  152. get => fileInfo; set {
  153. fileInfo = value;
  154. System.Text.StringBuilder sb = null;
  155. if (fileInfo is FileInfo f) {
  156. Title = $"File: {f.Name}";
  157. sb = new System.Text.StringBuilder ();
  158. sb.AppendLine ($"Path: {f.DirectoryName}");
  159. sb.AppendLine ($"Size: {f.Length:N0} bytes");
  160. sb.AppendLine ($"Modified: {f.LastWriteTime}");
  161. sb.AppendLine ($"Created: {f.CreationTime}");
  162. }
  163. if (fileInfo is DirectoryInfo dir) {
  164. Title = $"Directory: {dir.Name}";
  165. sb = new System.Text.StringBuilder ();
  166. sb.AppendLine ($"Path: {dir?.FullName}");
  167. sb.AppendLine ($"Modified: {dir.LastWriteTime}");
  168. sb.AppendLine ($"Created: {dir.CreationTime}");
  169. }
  170. details.Text = sb.ToString ();
  171. }
  172. }
  173. }
  174. private void ShowPropertiesOf (FileSystemInfo fileSystemInfo)
  175. {
  176. detailsFrame.FileInfo = fileSystemInfo;
  177. //if (fileSystemInfo is FileInfo f) {
  178. // System.Text.StringBuilder sb = new System.Text.StringBuilder ();
  179. // sb.AppendLine ($"Path:{f.DirectoryName}");
  180. // sb.AppendLine ($"Size:{f.Length:N0} bytes");
  181. // sb.AppendLine ($"Modified:{f.LastWriteTime}");
  182. // sb.AppendLine ($"Created:{f.CreationTime}");
  183. // MessageBox.Query (f.Name, sb.ToString (), "Close");
  184. //}
  185. //if (fileSystemInfo is DirectoryInfo dir) {
  186. // System.Text.StringBuilder sb = new System.Text.StringBuilder ();
  187. // sb.AppendLine ($"Path:{dir.Parent?.FullName}");
  188. // sb.AppendLine ($"Modified:{dir.LastWriteTime}");
  189. // sb.AppendLine ($"Created:{dir.CreationTime}");
  190. // MessageBox.Query (dir.Name, sb.ToString (), "Close");
  191. //}
  192. }
  193. private void SetupScrollBar ()
  194. {
  195. // When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
  196. treeViewFiles.Style.LeaveLastRow = true;
  197. var _scrollBar = new ScrollBarView (treeViewFiles, true);
  198. _scrollBar.ChangedPosition += () => {
  199. treeViewFiles.ScrollOffsetVertical = _scrollBar.Position;
  200. if (treeViewFiles.ScrollOffsetVertical != _scrollBar.Position) {
  201. _scrollBar.Position = treeViewFiles.ScrollOffsetVertical;
  202. }
  203. treeViewFiles.SetNeedsDisplay ();
  204. };
  205. _scrollBar.OtherScrollBarView.ChangedPosition += () => {
  206. treeViewFiles.ScrollOffsetHorizontal = _scrollBar.OtherScrollBarView.Position;
  207. if (treeViewFiles.ScrollOffsetHorizontal != _scrollBar.OtherScrollBarView.Position) {
  208. _scrollBar.OtherScrollBarView.Position = treeViewFiles.ScrollOffsetHorizontal;
  209. }
  210. treeViewFiles.SetNeedsDisplay ();
  211. };
  212. treeViewFiles.DrawContent += (e) => {
  213. _scrollBar.Size = treeViewFiles.ContentHeight;
  214. _scrollBar.Position = treeViewFiles.ScrollOffsetVertical;
  215. _scrollBar.OtherScrollBarView.Size = treeViewFiles.GetContentWidth (true);
  216. _scrollBar.OtherScrollBarView.Position = treeViewFiles.ScrollOffsetHorizontal;
  217. _scrollBar.Refresh ();
  218. };
  219. }
  220. private void SetupFileTree ()
  221. {
  222. // setup delegates
  223. treeViewFiles.TreeBuilder = new DelegateTreeBuilder<FileSystemInfo> (
  224. // Determines how to compute children of any given branch
  225. GetChildren,
  226. // As a shortcut to enumerating half the file system, tell tree that all directories are expandable (even if they turn out to be empty later on)
  227. (o) => o is DirectoryInfo
  228. );
  229. // Determines how to represent objects as strings on the screen
  230. treeViewFiles.AspectGetter = FileSystemAspectGetter;
  231. treeViewFiles.AddObjects (DriveInfo.GetDrives ().Select (d => d.RootDirectory));
  232. }
  233. private void TreeViewFiles_ObjectActivated (ObjectActivatedEventArgs<FileSystemInfo> obj)
  234. {
  235. ShowPropertiesOf (obj.ActivatedObject);
  236. }
  237. private void ShowLines ()
  238. {
  239. miShowLines.Checked = !miShowLines.Checked;
  240. treeViewFiles.Style.ShowBranchLines = miShowLines.Checked;
  241. treeViewFiles.SetNeedsDisplay ();
  242. }
  243. private void SetExpandableSymbols (Rune? expand, Rune? collapse)
  244. {
  245. miPlusMinus.Checked = expand == '+';
  246. miArrowSymbols.Checked = expand == '>';
  247. miNoSymbols.Checked = expand == null;
  248. miUnicodeSymbols.Checked = expand == 'ஹ';
  249. treeViewFiles.Style.ExpandableSymbol = expand;
  250. treeViewFiles.Style.CollapseableSymbol = collapse;
  251. treeViewFiles.SetNeedsDisplay ();
  252. }
  253. private void ShowColoredExpandableSymbols ()
  254. {
  255. miColoredSymbols.Checked = !miColoredSymbols.Checked;
  256. treeViewFiles.Style.ColorExpandSymbol = miColoredSymbols.Checked;
  257. treeViewFiles.SetNeedsDisplay ();
  258. }
  259. private void InvertExpandableSymbols ()
  260. {
  261. miInvertSymbols.Checked = !miInvertSymbols.Checked;
  262. treeViewFiles.Style.InvertExpandSymbolColors = miInvertSymbols.Checked;
  263. treeViewFiles.SetNeedsDisplay ();
  264. }
  265. private void SetFullName ()
  266. {
  267. miFullPaths.Checked = !miFullPaths.Checked;
  268. if (miFullPaths.Checked) {
  269. treeViewFiles.AspectGetter = (f) => f.FullName;
  270. } else {
  271. treeViewFiles.AspectGetter = (f) => f.Name;
  272. }
  273. treeViewFiles.SetNeedsDisplay ();
  274. }
  275. private void SetLeaveLastRow ()
  276. {
  277. miLeaveLastRow.Checked = !miLeaveLastRow.Checked;
  278. treeViewFiles.Style.LeaveLastRow = miLeaveLastRow.Checked;
  279. }
  280. private void SetCursor ()
  281. {
  282. miCursor.Checked = !miCursor.Checked;
  283. treeViewFiles.DesiredCursorVisibility = miCursor.Checked ? CursorVisibility.Default : CursorVisibility.Invisible;
  284. }
  285. private void SetMultiSelect ()
  286. {
  287. miMultiSelect.Checked = !miMultiSelect.Checked;
  288. treeViewFiles.MultiSelect = miMultiSelect.Checked;
  289. }
  290. private void SetCustomColors ()
  291. {
  292. var hidden = new ColorScheme {
  293. Focus = new Terminal.Gui.Attribute (Color.BrightRed, treeViewFiles.ColorScheme.Focus.Background),
  294. Normal = new Terminal.Gui.Attribute (Color.BrightYellow, treeViewFiles.ColorScheme.Normal.Background),
  295. };
  296. miCustomColors.Checked = !miCustomColors.Checked;
  297. if (miCustomColors.Checked) {
  298. treeViewFiles.ColorGetter = (m) => {
  299. if (m is DirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
  300. if (m is FileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
  301. return null;
  302. };
  303. } else {
  304. treeViewFiles.ColorGetter = null;
  305. }
  306. treeViewFiles.SetNeedsDisplay ();
  307. }
  308. private void SetCheckHighlightModelTextOnly ()
  309. {
  310. treeViewFiles.Style.HighlightModelTextOnly = !treeViewFiles.Style.HighlightModelTextOnly;
  311. miHighlightModelTextOnly.Checked = treeViewFiles.Style.HighlightModelTextOnly;
  312. treeViewFiles.SetNeedsDisplay ();
  313. }
  314. private IEnumerable<FileSystemInfo> GetChildren (FileSystemInfo model)
  315. {
  316. // If it is a directory it's children are all contained files and dirs
  317. if (model is DirectoryInfo d) {
  318. try {
  319. return d.GetFileSystemInfos ()
  320. //show directories first
  321. .OrderBy (a => a is DirectoryInfo ? 0 : 1)
  322. .ThenBy (b => b.Name);
  323. } catch (SystemException) {
  324. // Access violation or other error getting the file list for directory
  325. return Enumerable.Empty<FileSystemInfo> ();
  326. }
  327. }
  328. return Enumerable.Empty<FileSystemInfo> (); ;
  329. }
  330. private string FileSystemAspectGetter (FileSystemInfo model)
  331. {
  332. if (model is DirectoryInfo d) {
  333. return d.Name;
  334. }
  335. if (model is FileInfo f) {
  336. return f.Name;
  337. }
  338. return model.ToString ();
  339. }
  340. private void Quit ()
  341. {
  342. Application.RequestStop ();
  343. }
  344. }
  345. }