mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
662 lines
23 KiB
C#
662 lines
23 KiB
C#
#nullable enable
|
|
|
|
using System.IO.Abstractions;
|
|
using System.Text;
|
|
|
|
namespace UICatalog.Scenarios;
|
|
|
|
[ScenarioMetadata ("File System Explorer", "Hierarchical file system explorer demonstrating TreeView.")]
|
|
[ScenarioCategory ("Controls")]
|
|
[ScenarioCategory ("TreeView")]
|
|
[ScenarioCategory ("Files and IO")]
|
|
public class TreeViewFileSystem : Scenario
|
|
{
|
|
private readonly FileSystemIconProvider _iconProvider = new ();
|
|
private DetailsFrame? _detailsFrame;
|
|
private CheckBox? _miArrowSymbolsCheckBox;
|
|
private CheckBox? _miBasicIconsCheckBox;
|
|
private CheckBox? _miColoredSymbolsCheckBox;
|
|
private CheckBox? _miCursorCheckBox;
|
|
private CheckBox? _miCustomColorsCheckBox;
|
|
private CheckBox? _miFullPathsCheckBox;
|
|
private CheckBox? _miHighlightModelTextOnlyCheckBox;
|
|
private CheckBox? _miInvertSymbolsCheckBox;
|
|
private CheckBox? _miLeaveLastRowCheckBox;
|
|
private CheckBox? _miMultiSelectCheckBox;
|
|
private CheckBox? _miNerdIconsCheckBox;
|
|
private CheckBox? _miNoSymbolsCheckBox;
|
|
private CheckBox? _miPlusMinusCheckBox;
|
|
private CheckBox? _miShowLinesCheckBox;
|
|
private CheckBox? _miUnicodeIconsCheckBox;
|
|
|
|
/// <summary>A tree view where nodes are files and folders</summary>
|
|
private TreeView<IFileSystemInfo>? _treeViewFiles;
|
|
|
|
public override void Main ()
|
|
{
|
|
Application.Init ();
|
|
|
|
Window win = new ()
|
|
{
|
|
Title = GetName (),
|
|
Y = 1, // menu
|
|
Height = Dim.Fill ()
|
|
};
|
|
|
|
// MenuBar
|
|
MenuBar menu = new ();
|
|
|
|
_treeViewFiles = new () { X = 0, Y = Pos.Bottom (menu), Width = Dim.Percent (50), Height = Dim.Fill () };
|
|
_treeViewFiles.DrawLine += TreeViewFiles_DrawLine;
|
|
|
|
_treeViewFiles.VerticalScrollBar.AutoShow = false;
|
|
|
|
_detailsFrame = new (_iconProvider)
|
|
{
|
|
X = Pos.Right (_treeViewFiles),
|
|
Y = Pos.Top (_treeViewFiles),
|
|
Width = Dim.Fill (),
|
|
Height = Dim.Fill ()
|
|
};
|
|
|
|
win.Add (_detailsFrame);
|
|
_treeViewFiles.Activating += TreeViewFiles_Selecting;
|
|
_treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
|
|
_treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
|
|
|
|
SetupFileTree ();
|
|
|
|
// Setup menu checkboxes
|
|
_miFullPathsCheckBox = new ()
|
|
{
|
|
Title = "_Full Paths"
|
|
};
|
|
_miFullPathsCheckBox.CheckedStateChanged += (s, e) => SetFullName ();
|
|
|
|
_miMultiSelectCheckBox = new ()
|
|
{
|
|
Title = "_Multi Select",
|
|
CheckedState = CheckState.Checked
|
|
};
|
|
_miMultiSelectCheckBox.CheckedStateChanged += (s, e) => SetMultiSelect ();
|
|
|
|
_miShowLinesCheckBox = new ()
|
|
{
|
|
Title = "_Show Lines",
|
|
CheckedState = CheckState.Checked
|
|
};
|
|
_miShowLinesCheckBox.CheckedStateChanged += (s, e) => ShowLines ();
|
|
|
|
_miPlusMinusCheckBox = new ()
|
|
{
|
|
Title = "_Plus Minus Symbols",
|
|
CheckedState = CheckState.Checked
|
|
};
|
|
_miPlusMinusCheckBox.CheckedStateChanged += (s, e) => SetExpandableSymbols ((Rune)'+', (Rune)'-');
|
|
|
|
_miArrowSymbolsCheckBox = new ()
|
|
{
|
|
Title = "_Arrow Symbols"
|
|
};
|
|
_miArrowSymbolsCheckBox.CheckedStateChanged += (s, e) => SetExpandableSymbols ((Rune)'>', (Rune)'v');
|
|
|
|
_miNoSymbolsCheckBox = new ()
|
|
{
|
|
Title = "_No Symbols"
|
|
};
|
|
_miNoSymbolsCheckBox.CheckedStateChanged += (s, e) => SetExpandableSymbols (default (Rune), null);
|
|
|
|
_miColoredSymbolsCheckBox = new ()
|
|
{
|
|
Title = "_Colored Symbols"
|
|
};
|
|
_miColoredSymbolsCheckBox.CheckedStateChanged += (s, e) => ShowColoredExpandableSymbols ();
|
|
|
|
_miInvertSymbolsCheckBox = new ()
|
|
{
|
|
Title = "_Invert Symbols"
|
|
};
|
|
_miInvertSymbolsCheckBox.CheckedStateChanged += (s, e) => InvertExpandableSymbols ();
|
|
|
|
_miBasicIconsCheckBox = new ()
|
|
{
|
|
Title = "_Basic Icons"
|
|
};
|
|
_miBasicIconsCheckBox.CheckedStateChanged += (s, e) => SetNoIcons ();
|
|
|
|
_miUnicodeIconsCheckBox = new ()
|
|
{
|
|
Title = "_Unicode Icons"
|
|
};
|
|
_miUnicodeIconsCheckBox.CheckedStateChanged += (s, e) => SetUnicodeIcons ();
|
|
|
|
_miNerdIconsCheckBox = new ()
|
|
{
|
|
Title = "_Nerd Icons"
|
|
};
|
|
_miNerdIconsCheckBox.CheckedStateChanged += (s, e) => SetNerdIcons ();
|
|
|
|
_miLeaveLastRowCheckBox = new ()
|
|
{
|
|
Title = "_Leave Last Row",
|
|
CheckedState = CheckState.Checked
|
|
};
|
|
_miLeaveLastRowCheckBox.CheckedStateChanged += (s, e) => SetLeaveLastRow ();
|
|
|
|
_miHighlightModelTextOnlyCheckBox = new ()
|
|
{
|
|
Title = "_Highlight Model Text Only"
|
|
};
|
|
_miHighlightModelTextOnlyCheckBox.CheckedStateChanged += (s, e) => SetCheckHighlightModelTextOnly ();
|
|
|
|
_miCustomColorsCheckBox = new ()
|
|
{
|
|
Title = "C_ustom Colors Hidden Files"
|
|
};
|
|
_miCustomColorsCheckBox.CheckedStateChanged += (s, e) => SetCustomColors ();
|
|
|
|
_miCursorCheckBox = new ()
|
|
{
|
|
Title = "Curs_or (MultiSelect only)"
|
|
};
|
|
_miCursorCheckBox.CheckedStateChanged += (s, e) => SetCursor ();
|
|
|
|
menu.Add (
|
|
new MenuBarItem (
|
|
"_File",
|
|
[
|
|
new MenuItem
|
|
{
|
|
Title = "_Quit",
|
|
Key = Application.QuitKey,
|
|
Action = Quit
|
|
}
|
|
]
|
|
)
|
|
);
|
|
|
|
menu.Add (
|
|
new MenuBarItem (
|
|
"_View",
|
|
[
|
|
new MenuItem
|
|
{
|
|
CommandView = _miFullPathsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miMultiSelectCheckBox
|
|
}
|
|
]
|
|
)
|
|
);
|
|
|
|
menu.Add (
|
|
new MenuBarItem (
|
|
"_Style",
|
|
[
|
|
new MenuItem
|
|
{
|
|
CommandView = _miShowLinesCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miPlusMinusCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miArrowSymbolsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miNoSymbolsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miColoredSymbolsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miInvertSymbolsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miBasicIconsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miUnicodeIconsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miNerdIconsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miLeaveLastRowCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miHighlightModelTextOnlyCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miCustomColorsCheckBox
|
|
},
|
|
new MenuItem
|
|
{
|
|
CommandView = _miCursorCheckBox
|
|
}
|
|
]
|
|
)
|
|
);
|
|
|
|
win.Add (menu, _treeViewFiles);
|
|
_treeViewFiles.GoToFirst ();
|
|
_treeViewFiles.Expand ();
|
|
|
|
_treeViewFiles.SetFocus ();
|
|
|
|
UpdateIconCheckedness ();
|
|
|
|
Application.Run (win);
|
|
win.Dispose ();
|
|
Application.Shutdown ();
|
|
}
|
|
|
|
private string AspectGetter (IFileSystemInfo f) => (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim ();
|
|
|
|
private void InvertExpandableSymbols ()
|
|
{
|
|
if (_treeViewFiles is null || _miInvertSymbolsCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.Style.InvertExpandSymbolColors = _miInvertSymbolsCheckBox.CheckedState == CheckState.Checked;
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void Quit () { Application.RequestStop (); }
|
|
|
|
private void SetCheckHighlightModelTextOnly ()
|
|
{
|
|
if (_treeViewFiles is null || _miHighlightModelTextOnlyCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.Style.HighlightModelTextOnly = _miHighlightModelTextOnlyCheckBox.CheckedState == CheckState.Checked;
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void SetCursor ()
|
|
{
|
|
if (_treeViewFiles is null || _miCursorCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.CursorVisibility =
|
|
_miCursorCheckBox.CheckedState == CheckState.Checked ? CursorVisibility.Default : CursorVisibility.Invisible;
|
|
}
|
|
|
|
private void SetCustomColors ()
|
|
{
|
|
if (_treeViewFiles is null || _miCustomColorsCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_miCustomColorsCheckBox.CheckedState == CheckState.Checked)
|
|
{
|
|
_treeViewFiles.ColorGetter = m =>
|
|
{
|
|
if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
|
|
{
|
|
return new ()
|
|
{
|
|
Focus = new (
|
|
Color.BrightRed,
|
|
_treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
|
|
),
|
|
Normal = new (
|
|
Color.BrightYellow,
|
|
_treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
|
|
)
|
|
};
|
|
}
|
|
|
|
if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
|
|
{
|
|
return new ()
|
|
{
|
|
Focus = new (
|
|
Color.BrightRed,
|
|
_treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
|
|
),
|
|
Normal = new (
|
|
Color.BrightYellow,
|
|
_treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
|
|
)
|
|
};
|
|
}
|
|
|
|
return null;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
_treeViewFiles.ColorGetter = null;
|
|
}
|
|
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void SetExpandableSymbols (Rune expand, Rune? collapse)
|
|
{
|
|
if (_treeViewFiles is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_miPlusMinusCheckBox is { })
|
|
{
|
|
_miPlusMinusCheckBox.CheckedState = expand.Value == '+' ? CheckState.Checked : CheckState.UnChecked;
|
|
}
|
|
|
|
if (_miArrowSymbolsCheckBox is { })
|
|
{
|
|
_miArrowSymbolsCheckBox.CheckedState = expand.Value == '>' ? CheckState.Checked : CheckState.UnChecked;
|
|
}
|
|
|
|
if (_miNoSymbolsCheckBox is { })
|
|
{
|
|
_miNoSymbolsCheckBox.CheckedState = expand.Value == default (int) ? CheckState.Checked : CheckState.UnChecked;
|
|
}
|
|
|
|
_treeViewFiles.Style.ExpandableSymbol = expand;
|
|
_treeViewFiles.Style.CollapseableSymbol = collapse;
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void SetFullName ()
|
|
{
|
|
if (_treeViewFiles is null || _miFullPathsCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_miFullPathsCheckBox.CheckedState == CheckState.Checked)
|
|
{
|
|
_treeViewFiles.AspectGetter = f => f.FullName;
|
|
}
|
|
else
|
|
{
|
|
_treeViewFiles.AspectGetter = f => f.Name;
|
|
}
|
|
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void SetLeaveLastRow ()
|
|
{
|
|
if (_treeViewFiles is null || _miLeaveLastRowCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.Style.LeaveLastRow = _miLeaveLastRowCheckBox.CheckedState == CheckState.Checked;
|
|
}
|
|
|
|
private void SetMultiSelect ()
|
|
{
|
|
if (_treeViewFiles is null || _miMultiSelectCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.MultiSelect = _miMultiSelectCheckBox.CheckedState == CheckState.Checked;
|
|
}
|
|
|
|
private void SetNerdIcons ()
|
|
{
|
|
_iconProvider.UseNerdIcons = true;
|
|
UpdateIconCheckedness ();
|
|
}
|
|
|
|
private void SetNoIcons ()
|
|
{
|
|
_iconProvider.UseUnicodeCharacters = false;
|
|
_iconProvider.UseNerdIcons = false;
|
|
UpdateIconCheckedness ();
|
|
}
|
|
|
|
private void SetUnicodeIcons ()
|
|
{
|
|
_iconProvider.UseUnicodeCharacters = true;
|
|
UpdateIconCheckedness ();
|
|
}
|
|
|
|
private void SetupFileTree ()
|
|
{
|
|
if (_treeViewFiles is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// setup how to build tree
|
|
FileSystem fs = new ();
|
|
|
|
IEnumerable<IDirectoryInfo> rootDirs =
|
|
DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
|
|
_treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
|
|
_treeViewFiles.AddObjects (rootDirs);
|
|
|
|
// Determines how to represent objects as strings on the screen
|
|
_treeViewFiles.AspectGetter = AspectGetter;
|
|
|
|
_iconProvider.IsOpenGetter = _treeViewFiles.IsExpanded;
|
|
}
|
|
|
|
private void ShowColoredExpandableSymbols ()
|
|
{
|
|
if (_treeViewFiles is null || _miColoredSymbolsCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.Style.ColorExpandSymbol = _miColoredSymbolsCheckBox.CheckedState == CheckState.Checked;
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
|
|
{
|
|
PopoverMenu contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
|
|
|
|
// Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
|
|
// and the context menu is disposed when it is closed.
|
|
_detailsFrame?.App?.Popover?.Register (contextMenu);
|
|
|
|
Application.Invoke (() => contextMenu?.MakeVisible (screenPoint));
|
|
}
|
|
|
|
private void ShowLines ()
|
|
{
|
|
if (_treeViewFiles is null || _miShowLinesCheckBox is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_treeViewFiles.Style.ShowBranchLines = _miShowLinesCheckBox.CheckedState == CheckState.Checked;
|
|
_treeViewFiles.SetNeedsDraw ();
|
|
}
|
|
|
|
private void ShowPropertiesOf (IFileSystemInfo fileSystemInfo)
|
|
{
|
|
if (_detailsFrame is { })
|
|
{
|
|
_detailsFrame.FileInfo = fileSystemInfo;
|
|
}
|
|
}
|
|
|
|
private void TreeViewFiles_DrawLine (object? sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
|
|
{
|
|
// Render directory icons in yellow
|
|
if (e.Model is IDirectoryInfo d)
|
|
{
|
|
if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters)
|
|
{
|
|
if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.Cells.Count)
|
|
{
|
|
Cell cell = e.Cells [e.IndexOfModelText];
|
|
|
|
cell.Attribute = new Attribute (
|
|
Color.BrightYellow,
|
|
cell.Attribute!.Value.Background,
|
|
cell.Attribute!.Value.Style
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TreeViewFiles_KeyPress (object? sender, Key obj)
|
|
{
|
|
if (_treeViewFiles is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask))
|
|
{
|
|
IFileSystemInfo? selected = _treeViewFiles.SelectedObject;
|
|
|
|
// nothing is selected
|
|
if (selected is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int? location = _treeViewFiles.GetObjectRow (selected);
|
|
|
|
//selected object is offscreen or somehow not found
|
|
if (location is null || location < 0 || location > _treeViewFiles.Frame.Height)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ShowContextMenu (
|
|
new (
|
|
5 + _treeViewFiles.Frame.X,
|
|
location.Value + _treeViewFiles.Frame.Y + 2
|
|
),
|
|
selected
|
|
);
|
|
}
|
|
}
|
|
|
|
private void TreeViewFiles_Selecting (object? sender, CommandEventArgs e)
|
|
{
|
|
if (_treeViewFiles is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only handle mouse clicks
|
|
if (e.Context is not CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouse })
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if user right clicks
|
|
if (mouse.Flags.HasFlag (MouseFlags.Button3Clicked))
|
|
{
|
|
IFileSystemInfo? rightClicked = _treeViewFiles.GetObjectOnRow (mouse.Position.Y);
|
|
|
|
// nothing was clicked
|
|
if (rightClicked is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ShowContextMenu (
|
|
new (
|
|
mouse.Position.X + _treeViewFiles.Frame.X,
|
|
mouse.Position.Y + _treeViewFiles.Frame.Y + 2
|
|
),
|
|
rightClicked
|
|
);
|
|
}
|
|
}
|
|
|
|
private void TreeViewFiles_SelectionChanged (object? sender, SelectionChangedEventArgs<IFileSystemInfo> e) { ShowPropertiesOf (e.NewValue); }
|
|
|
|
private void UpdateIconCheckedness ()
|
|
{
|
|
if (_miBasicIconsCheckBox is { })
|
|
{
|
|
_miBasicIconsCheckBox.CheckedState = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters
|
|
? CheckState.Checked
|
|
: CheckState.UnChecked;
|
|
}
|
|
|
|
if (_miUnicodeIconsCheckBox is { })
|
|
{
|
|
_miUnicodeIconsCheckBox.CheckedState = _iconProvider.UseUnicodeCharacters ? CheckState.Checked : CheckState.UnChecked;
|
|
}
|
|
|
|
if (_miNerdIconsCheckBox is { })
|
|
{
|
|
_miNerdIconsCheckBox.CheckedState = _iconProvider.UseNerdIcons ? CheckState.Checked : CheckState.UnChecked;
|
|
}
|
|
|
|
_treeViewFiles?.SetNeedsDraw ();
|
|
}
|
|
|
|
private class DetailsFrame : FrameView
|
|
{
|
|
private readonly FileSystemIconProvider _iconProvider;
|
|
private IFileSystemInfo? _fileInfo;
|
|
|
|
public DetailsFrame (FileSystemIconProvider iconProvider)
|
|
{
|
|
Title = "Details";
|
|
Visible = true;
|
|
CanFocus = true;
|
|
_iconProvider = iconProvider;
|
|
}
|
|
|
|
public IFileSystemInfo? FileInfo
|
|
{
|
|
get => _fileInfo;
|
|
set
|
|
{
|
|
_fileInfo = value;
|
|
StringBuilder? sb = null;
|
|
|
|
if (_fileInfo is IFileInfo f)
|
|
{
|
|
Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
|
|
sb = new ();
|
|
sb.AppendLine ($"Path:\n {f.FullName}\n");
|
|
sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
|
|
sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
|
|
sb.AppendLine ($"Created:\n {f.CreationTime}");
|
|
}
|
|
|
|
if (_fileInfo is IDirectoryInfo dir)
|
|
{
|
|
Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
|
|
sb = new ();
|
|
sb.AppendLine ($"Path:\n {dir.FullName}\n");
|
|
sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
|
|
sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
|
|
}
|
|
|
|
Text = sb?.ToString () ?? string.Empty;
|
|
}
|
|
}
|
|
}
|
|
}
|