mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-25 22:21:49 +01:00
* Use new Lock objects * Fix CA1513: Use ObjectDisposedException.ThrowIf * Fix CA1860: Avoid using 'Enumerable.Any()' extension method * Fix IDE0028: Use collection initializers or expressions * Fix CA2263: Prefer generic overload when type is known * Fix CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons * Fix IDE0270: Null check can be simplified * Fix IDE0280: Use 'nameof' * Fix IDE0009: Add '.this' * Fix IDE0007: Use 'var' instead of explicit type * Fix IDE0062: Make local function static * Fix CA1859: Use concrete types when possible for improved performance * Fix IDE0066: Use switch expression Only applied to where it doesn't look horrendous. * Use is over switch * Fix CA1847: Use String.Contains(char) instead of String.Contains(string) with single characters * Fix SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. * Fix CA1866: Use 'string.EndsWith(char)' instead of 'string.EndsWith(string)' when you have a string with a single char * Fix IDE0057: Substring can be simplified * Fix IDE0059: Remove unnecessary value assignment * Fix CA1510: Use ArgumentNullException throw helper * Fix IDE0300: Use collection expression for array * Fix IDE0250: Struct can be made 'readonly' * Fix IDE0018: Inline variable declaration * Fix CA1850: Prefer static HashData method over ComputeHash * Fi CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' * Update ModuleLog instantiations * Organize usings
457 lines
14 KiB
C#
457 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
|
|
using Dalamud.Utility;
|
|
|
|
namespace Dalamud.Interface.ImGuiFileDialog;
|
|
|
|
/// <summary>
|
|
/// A file or folder picker.
|
|
/// </summary>
|
|
public partial class FileDialog
|
|
{
|
|
private readonly Lock filesLock = new();
|
|
|
|
private readonly DriveListLoader driveListLoader = new();
|
|
|
|
private readonly List<FileStruct> files = [];
|
|
private readonly List<FileStruct> filteredFiles = [];
|
|
|
|
private SortingField currentSortingField = SortingField.FileName;
|
|
|
|
/// <summary> Fired whenever the sorting field changes. </summary>
|
|
public event Action<SortingField>? SortOrderChanged;
|
|
|
|
/// <summary> The sorting type of the file selector. </summary>
|
|
public enum SortingField
|
|
{
|
|
/// <summary> No sorting specified. </summary>
|
|
None = 0,
|
|
|
|
/// <summary> Sort for ascending file names in culture-specific order. </summary>
|
|
FileName = 1,
|
|
|
|
/// <summary> Sort for ascending file types in culture-specific order. </summary>
|
|
Type = 2,
|
|
|
|
/// <summary> Sort for ascending file sizes. </summary>
|
|
Size = 3,
|
|
|
|
/// <summary> Sort for ascending last update dates. </summary>
|
|
Date = 4,
|
|
|
|
/// <summary> Sort for descending file names in culture-specific order. </summary>
|
|
FileNameDescending = 5,
|
|
|
|
/// <summary> Sort for descending file types in culture-specific order. </summary>
|
|
TypeDescending = 6,
|
|
|
|
/// <summary> Sort for descending file sizes. </summary>
|
|
SizeDescending = 7,
|
|
|
|
/// <summary> Sort for descending last update dates. </summary>
|
|
DateDescending = 8,
|
|
}
|
|
|
|
private enum FileStructType
|
|
{
|
|
File,
|
|
Directory,
|
|
}
|
|
|
|
/// <summary> Specify the current and subsequent sort order. </summary>
|
|
/// <param name="sortingField"> The new sort order. None is invalid and will not have any effect. </param>
|
|
public void SortFields(SortingField sortingField)
|
|
{
|
|
Comparison<FileStruct>? sortFunc = sortingField switch
|
|
{
|
|
SortingField.FileName => SortByFileNameAsc,
|
|
SortingField.FileNameDescending => SortByFileNameDesc,
|
|
SortingField.Type => SortByTypeAsc,
|
|
SortingField.TypeDescending => SortByTypeDesc,
|
|
SortingField.Size => SortBySizeAsc,
|
|
SortingField.SizeDescending => SortBySizeDesc,
|
|
SortingField.Date => SortByDateAsc,
|
|
SortingField.DateDescending => SortByDateDesc,
|
|
_ => null,
|
|
};
|
|
|
|
if (sortFunc is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.files.Sort(sortFunc);
|
|
this.currentSortingField = sortingField;
|
|
this.ApplyFilteringOnFileList();
|
|
this.SortOrderChanged?.InvokeSafely(this.currentSortingField);
|
|
}
|
|
|
|
private static string ComposeNewPath(List<string> decomposition)
|
|
{
|
|
switch (decomposition.Count)
|
|
{
|
|
// Handle UNC paths (network paths)
|
|
case >= 2 when string.IsNullOrEmpty(decomposition[0]) && string.IsNullOrEmpty(decomposition[1]):
|
|
var pathParts = new List<string>(decomposition);
|
|
pathParts.RemoveRange(0, 2);
|
|
|
|
// Can not access server level or UNC root
|
|
if (pathParts.Count <= 1)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return $@"\\{string.Join('\\', pathParts)}";
|
|
case 1:
|
|
var drivePath = decomposition[0];
|
|
if (drivePath[^1] != Path.DirectorySeparatorChar)
|
|
{ // turn C: into C:\
|
|
drivePath += Path.DirectorySeparatorChar;
|
|
}
|
|
|
|
return drivePath;
|
|
default: return Path.Combine(decomposition.ToArray());
|
|
}
|
|
}
|
|
|
|
private static FileStruct GetFile(FileInfo file, string path)
|
|
=> new()
|
|
{
|
|
FileName = file.Name,
|
|
FilePath = path,
|
|
FileModifiedDate = FormatModifiedDate(file.LastWriteTime),
|
|
FileSize = file.Length,
|
|
FormattedFileSize = BytesToString(file.Length),
|
|
Type = FileStructType.File,
|
|
Ext = file.Extension.Trim('.'),
|
|
};
|
|
|
|
private static FileStruct GetDir(DirectoryInfo dir, string path)
|
|
=> new()
|
|
{
|
|
FileName = dir.Name,
|
|
FilePath = path,
|
|
FileModifiedDate = FormatModifiedDate(dir.LastWriteTime),
|
|
FileSize = 0,
|
|
FormattedFileSize = string.Empty,
|
|
Type = FileStructType.Directory,
|
|
Ext = string.Empty,
|
|
};
|
|
|
|
private static int SortByFileNameDesc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.FileName[0] is '.')
|
|
{
|
|
if (b.FileName[0] is not '.')
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (a.FileName.Length is 1)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (b.FileName.Length is 1)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return -1 * string.Compare(a.FileName[1..], b.FileName[1..], StringComparison.CurrentCulture);
|
|
}
|
|
|
|
if (b.FileName[0] is '.')
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return a.Type is FileStructType.Directory ? 1 : -1;
|
|
}
|
|
|
|
return -string.Compare(a.FileName, b.FileName, StringComparison.CurrentCulture);
|
|
}
|
|
|
|
private static int SortByFileNameAsc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.FileName[0] is '.')
|
|
{
|
|
if (b.FileName[0] is not '.')
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (a.FileName.Length is 1)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (b.FileName.Length is 1)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return string.Compare(a.FileName[1..], b.FileName[1..], StringComparison.CurrentCulture);
|
|
}
|
|
|
|
if (b.FileName[0] is '.')
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return a.Type is FileStructType.Directory ? -1 : 1;
|
|
}
|
|
|
|
return string.Compare(a.FileName, b.FileName, StringComparison.CurrentCulture);
|
|
}
|
|
|
|
private static int SortByTypeDesc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return (a.Type == FileStructType.Directory) ? 1 : -1;
|
|
}
|
|
|
|
return string.Compare(a.Ext, b.Ext, StringComparison.CurrentCulture);
|
|
}
|
|
|
|
private static int SortByTypeAsc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return (a.Type == FileStructType.Directory) ? -1 : 1;
|
|
}
|
|
|
|
return -string.Compare(a.Ext, b.Ext, StringComparison.CurrentCulture);
|
|
}
|
|
|
|
private static int SortBySizeDesc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return (a.Type is FileStructType.Directory) ? 1 : -1;
|
|
}
|
|
|
|
return a.FileSize.CompareTo(b.FileSize);
|
|
}
|
|
|
|
private static int SortBySizeAsc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return (a.Type is FileStructType.Directory) ? -1 : 1;
|
|
}
|
|
|
|
return -a.FileSize.CompareTo(b.FileSize);
|
|
}
|
|
|
|
private static int SortByDateDesc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return (a.Type == FileStructType.Directory) ? 1 : -1;
|
|
}
|
|
|
|
return string.Compare(a.FileModifiedDate, b.FileModifiedDate, StringComparison.CurrentCulture);
|
|
}
|
|
|
|
private static int SortByDateAsc(FileStruct a, FileStruct b)
|
|
{
|
|
switch (a.FileName, b.FileName)
|
|
{
|
|
case ("..", ".."): return 0;
|
|
case ("..", _): return -1;
|
|
case (_, ".."): return 1;
|
|
}
|
|
|
|
if (a.Type != b.Type)
|
|
{
|
|
return (a.Type == FileStructType.Directory) ? -1 : 1;
|
|
}
|
|
|
|
return -string.Compare(a.FileModifiedDate, b.FileModifiedDate, StringComparison.CurrentCulture);
|
|
}
|
|
|
|
private bool CreateDir(string dirPath)
|
|
{
|
|
var newPath = Path.Combine(this.currentPath, dirPath);
|
|
if (string.IsNullOrEmpty(newPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Directory.CreateDirectory(newPath);
|
|
return true;
|
|
}
|
|
|
|
private void ScanDir(string path)
|
|
{
|
|
if (!Directory.Exists(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.pathDecomposition.Count == 0)
|
|
{
|
|
this.SetCurrentDir(path);
|
|
}
|
|
|
|
if (this.pathDecomposition.Count > 0)
|
|
{
|
|
this.files.Clear();
|
|
|
|
if (this.pathDecomposition.Count > 1)
|
|
{
|
|
this.files.Add(new FileStruct
|
|
{
|
|
Type = FileStructType.Directory,
|
|
FilePath = path,
|
|
FileName = "..",
|
|
FileSize = 0,
|
|
FileModifiedDate = string.Empty,
|
|
FormattedFileSize = string.Empty,
|
|
Ext = string.Empty,
|
|
});
|
|
}
|
|
|
|
var dirInfo = new DirectoryInfo(path);
|
|
|
|
var dontShowHidden = this.flags.HasFlag(ImGuiFileDialogFlags.DontShowHiddenFiles);
|
|
|
|
foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name))
|
|
{
|
|
if (string.IsNullOrEmpty(dir.Name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (dontShowHidden && dir.Name[0] == '.')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this.files.Add(GetDir(dir, path));
|
|
}
|
|
|
|
foreach (var file in dirInfo.EnumerateFiles().OrderBy(f => f.Name))
|
|
{
|
|
if (string.IsNullOrEmpty(file.Name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (dontShowHidden && file.Name[0] == '.')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(file.Extension))
|
|
{
|
|
var ext = file.Extension;
|
|
if (this.filters.Count > 0 && !this.selectedFilter.Empty() && !this.selectedFilter.FilterExists(ext) && this.selectedFilter.Filter != ".*")
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
this.files.Add(GetFile(file, path));
|
|
}
|
|
|
|
this.SortFields(this.currentSortingField);
|
|
}
|
|
}
|
|
|
|
private IEnumerable<SideBarItem> GetDrives()
|
|
{
|
|
return this.driveListLoader.Drives.Select(drive => new SideBarItem(drive.Name, drive.Name, FontAwesomeIcon.Server));
|
|
}
|
|
|
|
private void SetupSideBar()
|
|
{
|
|
_ = this.driveListLoader.LoadDrivesAsync();
|
|
|
|
var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
|
|
|
|
this.quickAccess.Add(new SideBarItem("Desktop", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), FontAwesomeIcon.Desktop));
|
|
this.quickAccess.Add(new SideBarItem("Documents", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), FontAwesomeIcon.File));
|
|
|
|
if (!string.IsNullOrEmpty(personal))
|
|
{
|
|
this.quickAccess.Add(new SideBarItem("Downloads", Path.Combine(personal, "Downloads"), FontAwesomeIcon.Download));
|
|
}
|
|
|
|
this.quickAccess.Add(new SideBarItem("Favorites", Environment.GetFolderPath(Environment.SpecialFolder.Favorites), FontAwesomeIcon.Star));
|
|
this.quickAccess.Add(new SideBarItem("Music", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), FontAwesomeIcon.Music));
|
|
this.quickAccess.Add(new SideBarItem("Pictures", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), FontAwesomeIcon.Image));
|
|
this.quickAccess.Add(new SideBarItem("Videos", Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), FontAwesomeIcon.Video));
|
|
}
|
|
|
|
private SortingField GetNewSorting(int column)
|
|
=> column switch
|
|
{
|
|
0 when this.currentSortingField is SortingField.FileName => SortingField.FileNameDescending,
|
|
0 => SortingField.FileName,
|
|
1 when this.currentSortingField is SortingField.Type => SortingField.TypeDescending,
|
|
1 => SortingField.Type,
|
|
2 when this.currentSortingField is SortingField.Size => SortingField.SizeDescending,
|
|
2 => SortingField.Size,
|
|
3 when this.currentSortingField is SortingField.Date => SortingField.DateDescending,
|
|
3 => SortingField.Date,
|
|
_ => SortingField.None,
|
|
};
|
|
}
|