using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace CKAN.ConsoleUI.Toolkit {
public class ConsoleFileMultiSelectDialog : ConsoleDialog {
public ConsoleFileMultiSelectDialog(ConsoleTheme theme,
string title,
string startPath,
string filPat,
string toggleHeader,
string acceptTip)
: base(theme)
{
CenterHeader = () => title;
curDir = new DirectoryInfo(startPath);
filePattern = filPat;
int w = (Console.WindowWidth > idealW + (2 * hPad))
? idealW
: Console.WindowWidth - (2 * hPad);
int left = (Console.WindowWidth - w) / 2;
int right = -left;
SetDimensions(left, top, right, bottom);
AddObject(new ConsoleLabel(
left + 3, top + 2, left + 2 + labelW - 1,
() => Properties.Resources.FileSelectDirectory,
th => th.PopupBg,
th => th.PopupFg
));
pathField = new ConsoleField(
left + 3 + labelW + 1, top + 2, right - 3,
curDir.FullName
);
pathField.OnChange += pathFieldChanged;
AddObject(pathField);
AddObject(new ConsoleLabel(
left + 3, bottom - 1, right - 3,
() => string.Format(Properties.Resources.FileSelectCountFooter, chosenFiles.Count, CkanModule.FmtSize(totalChosenSize())),
th => th.PopupBg,
th => th.PopupFg
));
fileList = new ConsoleListBox<FileSystemInfo>(
left + 3, top + 4, right - 3, bottom - 2,
getFileList(),
new List<ConsoleListBoxColumn<FileSystemInfo>>() {
new ConsoleListBoxColumn<FileSystemInfo>(
toggleHeader, getRowSymbol, null, 8),
new ConsoleListBoxColumn<FileSystemInfo>(
Properties.Resources.FileSelectNameHeader,
getRowName, compareNames, null),
new ConsoleListBoxColumn<FileSystemInfo>(
Properties.Resources.FileSelectSizeHeader,
getLength,
(a, b) => {
var fb = b as FileInfo;
return a is not FileInfo fa
? (fb == null ? 0 : -1)
: (fb == null ? 1 : fa.Length.CompareTo(fb.Length));
},
9),
new ConsoleListBoxColumn<FileSystemInfo>(
Properties.Resources.FileSelectTimestampHeader,
fi => fi.LastWriteTime.ToString("yyyy-MM-dd"),
(a, b) => a.LastWriteTime.CompareTo(b.LastWriteTime),
10)
},
1, 1, ListSortDirection.Ascending
);
AddObject(fileList);
AddTip(Properties.Resources.Esc, Properties.Resources.Cancel);
AddBinding(Keys.Escape, sender =>
{
chosenFiles.Clear();
return false;
});
AddTip(ConsoleScreen.MainMenuKeyTip, Properties.Resources.Sort);
AddBinding(ConsoleScreen.MainMenuKeys, sender =>
{
fileList.SortMenu().Run(theme, right - 2, top + 2);
DrawBackground();
return true;
});
AddTip(Properties.Resources.Enter, Properties.Resources.FileSelectChangeDirectory, () => fileList.Selection != null && isDir(fileList.Selection));
AddTip(Properties.Resources.Enter, Properties.Resources.FileSelectSelect, () => fileList.Selection != null && !isDir(fileList.Selection));
AddBinding(Keys.Enter, sender => selectRow());
AddBinding(Keys.Space, sender => selectRow());
AddTip($"{Properties.Resources.Ctrl}+A", Properties.Resources.SelectAll);
AddBinding(Keys.CtrlA, sender =>
{
foreach (FileSystemInfo fi in contents) {
if (!isDir(fi)) {
if (fi is FileInfo file)
{
chosenFiles.Add(file);
}
}
}
return true;
});
AddTip($"{Properties.Resources.Ctrl}+D", Properties.Resources.DeselectAll, () => chosenFiles.Count > 0);
AddBinding(Keys.CtrlD, sender =>
{
if (chosenFiles.Count > 0) {
chosenFiles.Clear();
}
return true;
});
AddTip("F9", acceptTip, () => chosenFiles.Count > 0);
AddBinding(Keys.F9, sender => false);
}
private bool selectRow()
{
if (fileList.Selection != null && isDir(fileList.Selection)) {
if (fileList.Selection is DirectoryInfo di)
{
curDir = di;
pathField.Value = curDir.FullName;
fileList.SetData(getFileList(), true);
}
} else {
if (fileList.Selection is FileInfo fi)
{
if (!chosenFiles.Remove(fi))
{
chosenFiles.Add(fi);
}
}
}
return true;
}
private void pathFieldChanged(ConsoleField sender, string newValue)
{
if (Directory.Exists(newValue)) {
try {
curDir = new DirectoryInfo(newValue);
fileList.SetData(getFileList());
} catch { }
}
}
public new HashSet<FileInfo> Run(Action? process = null)
{
base.Run(process);
return chosenFiles;
}
[MemberNotNull(nameof(contents))]
private IList<FileSystemInfo> getFileList()
{
contents = new List<FileSystemInfo>();
if (curDir.Parent != null) {
contents.Add(curDir.Parent);
}
contents.AddRange(curDir.EnumerateDirectories());
contents.AddRange(curDir.EnumerateFiles(filePattern));
return contents;
}
private static bool isDir(FileSystemInfo fi)
=> fi.Attributes.HasFlag(FileAttributes.Directory);
private static bool pathEquals(FileSystemInfo? a, FileSystemInfo? b)
{
if (a == null || b == null) {
return false;
}
if (a == b) {
return true;
}
string pathA = a.FullName;
string pathB = b.FullName;
if (Platform.IsWindows) {
pathA = pathA.ToLower();
pathB = pathB.ToLower();
}
return pathA == pathB;
}
private string getLength(FileSystemInfo fi)
=> isDir(fi)
? Properties.Resources.FileSelectDirSize
: fi is FileInfo file
? CkanModule.FmtSize(file.Length)
: Properties.Resources.FileSelectDirSize;
private long totalChosenSize()
{
long val = 0;
foreach (FileInfo f in chosenFiles) {
val += f.Length;
}
return val;
}
private string getRowSymbol(FileSystemInfo fi)
=> !isDir(fi)
&& fi is FileInfo file
&& chosenFiles.Contains(file)
? chosen
: "";
private string getRowName(FileSystemInfo fi)
{
if (isDir(fi)) {
if (pathEquals(curDir.Parent, fi)) {
return $"..{Path.DirectorySeparatorChar}";
} else {
return $"{fi.Name}{Path.DirectorySeparatorChar}";
}
} else {
return fi.Name;
}
}
private int compareNames(FileSystemInfo a, FileSystemInfo b)
{
if (isDir(a)) {
if (isDir(b)) {
if (pathEquals(curDir.Parent, a)) {
return -1;
} else if (pathEquals(curDir.Parent, b)) {
return 1;
} else {
return a.Name.CompareTo(b.Name);
}
} else {
return -1;
}
} else {
if (isDir(b)) {
return 1;
} else {
return a.Name.CompareTo(b.Name);
}
}
}
private List<FileSystemInfo> contents;
private readonly ConsoleField pathField;
private readonly ConsoleListBox<FileSystemInfo> fileList;
private DirectoryInfo curDir;
private readonly HashSet<FileInfo> chosenFiles = new HashSet<FileInfo>();
private readonly string filePattern;
private static readonly string chosen = Symbols.checkmark;
private const int idealW = 76;
private static int labelW => Properties.Resources.FileSelectDirectory.Length;
private const int hPad = 2;
private const int top = 2;
private const int bottom = -3;
}
}