TreeViewFileSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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),
  74. Y = 0,
  75. Width = Dim.Fill (),
  76. Height = Dim.Fill (),
  77. };
  78. Win.Add (detailsFrame);
  79. treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
  80. treeViewFiles.KeyPress += TreeViewFiles_KeyPress;
  81. treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
  82. SetupFileTree ();
  83. Win.Add (treeViewFiles);
  84. treeViewFiles.GoToFirst ();
  85. treeViewFiles.Expand ();
  86. SetupScrollBar ();
  87. treeViewFiles.SetFocus ();
  88. }
  89. private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<FileSystemInfo> e)
  90. {
  91. ShowPropertiesOf (e.NewValue);
  92. }
  93. private void TreeViewFiles_KeyPress (View.KeyEventEventArgs obj)
  94. {
  95. if (obj.KeyEvent.Key == (Key.R | Key.CtrlMask)) {
  96. var selected = treeViewFiles.SelectedObject;
  97. // nothing is selected
  98. if (selected == null)
  99. return;
  100. var location = treeViewFiles.GetObjectRow (selected);
  101. //selected object is offscreen or somehow not found
  102. if (location == null || location < 0 || location > treeViewFiles.Frame.Height)
  103. return;
  104. ShowContextMenu (new Point (
  105. 5 + treeViewFiles.Frame.X,
  106. location.Value + treeViewFiles.Frame.Y + 2),
  107. selected);
  108. }
  109. }
  110. private void TreeViewFiles_MouseClick (View.MouseEventArgs obj)
  111. {
  112. // if user right clicks
  113. if (obj.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
  114. var rightClicked = treeViewFiles.GetObjectOnRow (obj.MouseEvent.Y);
  115. // nothing was clicked
  116. if (rightClicked == null)
  117. return;
  118. ShowContextMenu (new Point (
  119. obj.MouseEvent.X + treeViewFiles.Frame.X,
  120. obj.MouseEvent.Y + treeViewFiles.Frame.Y + 2),
  121. rightClicked);
  122. }
  123. }
  124. private void ShowContextMenu (Point screenPoint, FileSystemInfo forObject)
  125. {
  126. var menu = new ContextMenu ();
  127. menu.Position = screenPoint;
  128. menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) });
  129. Application.MainLoop.Invoke (menu.Show);
  130. }
  131. class DetailsFrame : FrameView {
  132. private FileSystemInfo fileInfo;
  133. public DetailsFrame ()
  134. {
  135. Title = "Details";
  136. Visible = true;
  137. CanFocus = true;
  138. }
  139. public FileSystemInfo FileInfo {
  140. get => fileInfo; set {
  141. fileInfo = value;
  142. System.Text.StringBuilder sb = null;
  143. if (fileInfo is FileInfo f) {
  144. Title = $"File: {f.Name}";
  145. sb = new System.Text.StringBuilder ();
  146. sb.AppendLine ($"Path:\n {f.FullName}\n");
  147. sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
  148. sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
  149. sb.AppendLine ($"Created:\n {f.CreationTime}");
  150. }
  151. if (fileInfo is DirectoryInfo dir) {
  152. Title = $"Directory: {dir.Name}";
  153. sb = new System.Text.StringBuilder ();
  154. sb.AppendLine ($"Path:\n {dir?.FullName}\n");
  155. sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
  156. sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
  157. }
  158. Text = sb.ToString ();
  159. }
  160. }
  161. }
  162. private void ShowPropertiesOf (FileSystemInfo fileSystemInfo)
  163. {
  164. detailsFrame.FileInfo = fileSystemInfo;
  165. }
  166. private void SetupScrollBar ()
  167. {
  168. // When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
  169. treeViewFiles.Style.LeaveLastRow = true;
  170. var _scrollBar = new ScrollBarView (treeViewFiles, true);
  171. _scrollBar.ChangedPosition += () => {
  172. treeViewFiles.ScrollOffsetVertical = _scrollBar.Position;
  173. if (treeViewFiles.ScrollOffsetVertical != _scrollBar.Position) {
  174. _scrollBar.Position = treeViewFiles.ScrollOffsetVertical;
  175. }
  176. treeViewFiles.SetNeedsDisplay ();
  177. };
  178. _scrollBar.OtherScrollBarView.ChangedPosition += () => {
  179. treeViewFiles.ScrollOffsetHorizontal = _scrollBar.OtherScrollBarView.Position;
  180. if (treeViewFiles.ScrollOffsetHorizontal != _scrollBar.OtherScrollBarView.Position) {
  181. _scrollBar.OtherScrollBarView.Position = treeViewFiles.ScrollOffsetHorizontal;
  182. }
  183. treeViewFiles.SetNeedsDisplay ();
  184. };
  185. treeViewFiles.DrawContent += (e) => {
  186. _scrollBar.Size = treeViewFiles.ContentHeight;
  187. _scrollBar.Position = treeViewFiles.ScrollOffsetVertical;
  188. _scrollBar.OtherScrollBarView.Size = treeViewFiles.GetContentWidth (true);
  189. _scrollBar.OtherScrollBarView.Position = treeViewFiles.ScrollOffsetHorizontal;
  190. _scrollBar.Refresh ();
  191. };
  192. }
  193. private void SetupFileTree ()
  194. {
  195. // setup delegates
  196. treeViewFiles.TreeBuilder = new DelegateTreeBuilder<FileSystemInfo> (
  197. // Determines how to compute children of any given branch
  198. GetChildren,
  199. // 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)
  200. (o) => o is DirectoryInfo
  201. );
  202. // Determines how to represent objects as strings on the screen
  203. treeViewFiles.AspectGetter = FileSystemAspectGetter;
  204. treeViewFiles.AddObjects (DriveInfo.GetDrives ().Select (d => d.RootDirectory));
  205. }
  206. private void ShowLines ()
  207. {
  208. miShowLines.Checked = !miShowLines.Checked;
  209. treeViewFiles.Style.ShowBranchLines = miShowLines.Checked;
  210. treeViewFiles.SetNeedsDisplay ();
  211. }
  212. private void SetExpandableSymbols (Rune? expand, Rune? collapse)
  213. {
  214. miPlusMinus.Checked = expand == '+';
  215. miArrowSymbols.Checked = expand == '>';
  216. miNoSymbols.Checked = expand == null;
  217. miUnicodeSymbols.Checked = expand == 'ஹ';
  218. treeViewFiles.Style.ExpandableSymbol = expand;
  219. treeViewFiles.Style.CollapseableSymbol = collapse;
  220. treeViewFiles.SetNeedsDisplay ();
  221. }
  222. private void ShowColoredExpandableSymbols ()
  223. {
  224. miColoredSymbols.Checked = !miColoredSymbols.Checked;
  225. treeViewFiles.Style.ColorExpandSymbol = miColoredSymbols.Checked;
  226. treeViewFiles.SetNeedsDisplay ();
  227. }
  228. private void InvertExpandableSymbols ()
  229. {
  230. miInvertSymbols.Checked = !miInvertSymbols.Checked;
  231. treeViewFiles.Style.InvertExpandSymbolColors = miInvertSymbols.Checked;
  232. treeViewFiles.SetNeedsDisplay ();
  233. }
  234. private void SetFullName ()
  235. {
  236. miFullPaths.Checked = !miFullPaths.Checked;
  237. if (miFullPaths.Checked) {
  238. treeViewFiles.AspectGetter = (f) => f.FullName;
  239. } else {
  240. treeViewFiles.AspectGetter = (f) => f.Name;
  241. }
  242. treeViewFiles.SetNeedsDisplay ();
  243. }
  244. private void SetLeaveLastRow ()
  245. {
  246. miLeaveLastRow.Checked = !miLeaveLastRow.Checked;
  247. treeViewFiles.Style.LeaveLastRow = miLeaveLastRow.Checked;
  248. }
  249. private void SetCursor ()
  250. {
  251. miCursor.Checked = !miCursor.Checked;
  252. treeViewFiles.DesiredCursorVisibility = miCursor.Checked ? CursorVisibility.Default : CursorVisibility.Invisible;
  253. }
  254. private void SetMultiSelect ()
  255. {
  256. miMultiSelect.Checked = !miMultiSelect.Checked;
  257. treeViewFiles.MultiSelect = miMultiSelect.Checked;
  258. }
  259. private void SetCustomColors ()
  260. {
  261. var hidden = new ColorScheme {
  262. Focus = new Terminal.Gui.Attribute (Color.BrightRed, treeViewFiles.ColorScheme.Focus.Background),
  263. Normal = new Terminal.Gui.Attribute (Color.BrightYellow, treeViewFiles.ColorScheme.Normal.Background),
  264. };
  265. miCustomColors.Checked = !miCustomColors.Checked;
  266. if (miCustomColors.Checked) {
  267. treeViewFiles.ColorGetter = (m) => {
  268. if (m is DirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
  269. if (m is FileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
  270. return null;
  271. };
  272. } else {
  273. treeViewFiles.ColorGetter = null;
  274. }
  275. treeViewFiles.SetNeedsDisplay ();
  276. }
  277. private void SetCheckHighlightModelTextOnly ()
  278. {
  279. treeViewFiles.Style.HighlightModelTextOnly = !treeViewFiles.Style.HighlightModelTextOnly;
  280. miHighlightModelTextOnly.Checked = treeViewFiles.Style.HighlightModelTextOnly;
  281. treeViewFiles.SetNeedsDisplay ();
  282. }
  283. private IEnumerable<FileSystemInfo> GetChildren (FileSystemInfo model)
  284. {
  285. // If it is a directory it's children are all contained files and dirs
  286. if (model is DirectoryInfo d) {
  287. try {
  288. return d.GetFileSystemInfos ()
  289. //show directories first
  290. .OrderBy (a => a is DirectoryInfo ? 0 : 1)
  291. .ThenBy (b => b.Name);
  292. } catch (SystemException) {
  293. // Access violation or other error getting the file list for directory
  294. return Enumerable.Empty<FileSystemInfo> ();
  295. }
  296. }
  297. return Enumerable.Empty<FileSystemInfo> (); ;
  298. }
  299. private string FileSystemAspectGetter (FileSystemInfo model)
  300. {
  301. if (model is DirectoryInfo d) {
  302. return d.Name;
  303. }
  304. if (model is FileInfo f) {
  305. return f.Name;
  306. }
  307. return model.ToString ();
  308. }
  309. private void Quit ()
  310. {
  311. Application.RequestStop ();
  312. }
  313. }
  314. }