HashCalculator.GUI/ViewModel.cs
2022-01-25 19:05:37 +01:00

582 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using TechnologyAssembler.Core.IO;
namespace HashCalculator.GUI
{
internal abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected void Notify(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
protected void SetField<X>(ref X field, X value, [CallerMemberName] string? propertyName = null)
{
if (propertyName == null)
{
throw new InvalidOperationException();
}
if (EqualityComparer<X>.Default.Equals(field, value))
{
return;
}
field = value;
Notify(propertyName);
}
protected void SetField<X>(ref X field, X value, Action onChange, [CallerMemberName] string? propertyName = null)
{
if (propertyName == null)
{
throw new InvalidOperationException();
}
if (EqualityComparer<X>.Default.Equals(field, value))
{
return;
}
field = value;
onChange();
Notify(propertyName);
}
#endregion
}
[SuppressMessage("Microsoft.Performance", "CA1812")]
internal class ViewModel : NotifyPropertyChanged
{
private static readonly Random _random = new();
public event EventHandler? ClearTracer;
public event EventHandler<OpenFileDialogEventArgs>? OpenFileDialog;
public MainInputViewModel MainInput { get; }
public BigInputViewModel BigEntryInput { get; }
private string? _filter;
public string? Filter
{
get => _filter;
set => SetField(ref _filter, value, () => FilterDisplayEntries.Execute(null));
}
private bool _gameObjectOnly;
public bool GameObjectOnly
{
get => _gameObjectOnly;
set => SetField(ref _gameObjectOnly, value, () => ConsiderTypeId &= !value);
}
private bool _considerTypeId;
public bool ConsiderTypeId
{
get => _considerTypeId;
set => SetField(ref _considerTypeId, value, () => GameObjectOnly &= !value);
}
public Command FilterDisplayEntries { get; }
private int _totalCount;
public int TotalCount
{
get => _totalCount;
set => SetField(ref _totalCount, value);
}
public ObservableSortedCollection<AssetEntry> DisplayEntries { get; } = new ObservableSortedCollection<AssetEntry>();
private string? _statusText;
public string StatusText
{
get => SuggestionString(_statusText);
set => SetField(ref _statusText, value);
}
private bool _isXml;
public bool IsXml
{
get => _isXml;
set => SetField(ref _isXml, value, () => IsXsd &= value);
}
private bool _isLoadingXml;
public bool IsLoadingXml
{
get => _isLoadingXml;
set => SetField(ref _isLoadingXml, value);
}
private bool _isXsd;
public bool IsXsd
{
get => _isXsd;
set => SetField(ref _isXsd, value, () =>
{
IsXml |= value;
ConsiderTypeId |= value;
});
}
public Command CancelXml { get; }
[SuppressMessage("Microsoft.Performance", "CA1822")]
public string LoadCsfText => $"{(Translator.HasProvider ? "" : "")} CSF";
public Command<MainWindow> LoadCsf { get; }
public Command ClearTraceText { get; }
public ViewModel()
{
var controller = new Controller(this);
MainInput = new MainInputViewModel(controller);
BigEntryInput = new BigInputViewModel(controller);
CancelXml = new Command(async () =>
{
try
{
CancelXml!.CanExecuteValue = false;
await controller.CancelLoadingXml().ConfigureAwait(true);
}
finally
{
CancelXml!.CanExecuteValue = true;
}
});
LoadCsf = new Command<MainWindow>(async window =>
{
try
{
LoadCsf!.CanExecuteValue = false;
var fileName = await controller.RequestOpenFile(string.Empty, ("*.csf;*.big", "CSF / BIG 文件"));
if (fileName is not null)
{
await Controller.LoadCsf(fileName).ConfigureAwait(true);
}
}
finally
{
LoadCsf!.CanExecuteValue = true;
}
});
FilterDisplayEntries = new Command(() =>
{
controller.UpdateEntries();
return Task.CompletedTask;
});
ClearTraceText = new Command(() =>
{
ClearTracer?.Invoke(this, EventArgs.Empty);
return Task.CompletedTask;
});
}
public Task<string?> RequestOpenFile(string title, IEnumerable<(string Extension, string? Description)> filters)
{
var data = new OpenFileDialogEventArgs(title, filters);
OpenFileDialog?.Invoke(this, data);
return data.Result;
}
public void NotifyCsfChange()
{
Notify(nameof(LoadCsfText));
FilterDisplayEntries.Execute(null);
}
public void AddNewItems(IEnumerable<AssetEntry> items)
{
DisplayEntries.SortedAddItems(items.Where(FilterDisplayEntry));
}
public void FilterCollection(HashSet<AssetEntry> orderedSource)
{
DisplayEntries.SortedRemoveItems(x => !orderedSource.Contains(x) || !FilterDisplayEntry(x));
DisplayEntries.SortedAddItems(orderedSource.Where(FilterDisplayEntry));
}
private bool FilterDisplayEntry(AssetEntry entry)
{
if (GameObjectOnly)
{
if (entry.Type != "GameObject")
{
return false;
}
}
if (string.IsNullOrEmpty(Filter))
{
return true;
}
foreach (var chunk in Filter!.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
if (entry.NameString.Contains(chunk, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (entry.InstanceIdString.Contains(chunk, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (Translator.HasProvider)
{
if (entry.LocalizedNames.IndexOf(chunk, StringComparison.CurrentCultureIgnoreCase) != -1)
{
return true;
}
}
if (!GameObjectOnly && ConsiderTypeId)
{
if (entry.TypeIdString.Contains(chunk, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
private static string SuggestionString(string? text)
{
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
var generated = $"{_random.NextDouble()}";
System.Diagnostics.Debug.WriteLine(generated);
if (generated.Contains("38") || generated.Contains("16"))
{
return "你们都是喂鱼的马甲!(";
}
if (generated.IndexOf('2') == 2)
{
return "本来以为两小时就能写完这个小工具没想到写了两个星期开始怀疑自己的智商orz";
}
if (generated.IndexOf('7') < 5)
{
return "小提示:在下方的素材列表里,选择一行或多行之后,直接按 Ctrl+C 就可以复制内容~";
}
if (generated.IndexOf('2') == 3)
{
return "温馨提示:请多留意一下自己的重工,不要让它卖自己";
}
return "不知道该显示些什么呢……";
}
}
internal class InputBarViewModel : NotifyPropertyChanged
{
private IEnumerable<InputEntry>? _items;
public virtual IEnumerable<InputEntry>? Items
{
get => _items;
set => SetField(ref _items, value);
}
private InputEntry? _selectedItem;
public virtual InputEntry? SelectedItem
{
get => _selectedItem;
set => SetField(ref _selectedItem, value);
}
private int _selectedIndex;
public virtual int SelectedIndex
{
get => _selectedIndex;
set => SetField(ref _selectedIndex, value);
}
private string? _text;
public virtual string? Text
{
get => _text;
set => SetField(ref _text, value);
}
}
internal class MainInputViewModel : InputBarViewModel
{
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
public override int SelectedIndex
{
get => base.SelectedIndex;
set
{
if (value == 0)
{
if (Items?.Count() <= 1)
{
value = -1;
}
else
{
value = base.SelectedIndex < value ? 1 : -1;
}
}
base.SelectedIndex = value;
}
}
public override string? Text
{
get => base.Text;
set
{
base.Text = value;
if (value != SelectedItem?.ToString())
{
UpdateList(value);
}
}
}
public Command<MainWindow> BrowseCommand { get; }
public Command<ViewModel> SelectCommand { get; }
public MainInputViewModel(Controller controller)
{
Items = Enumerable.Empty<InputEntry>();
BrowseCommand = new Command<MainWindow>(async window =>
{
var fileName = await controller.RequestOpenFile(string.Empty);
if (fileName is not null)
{
Text = fileName;
}
});
SelectCommand = new Command<ViewModel>(async viewModel =>
{
if (SelectedItem?.IsValid != true)
{
return;
}
try
{
BrowseCommand.CanExecuteValue = false;
SelectCommand!.CanExecuteValue = false;
await controller.OnMainInputDecided(SelectedItem).ConfigureAwait(true);
}
finally
{
BrowseCommand.CanExecuteValue = true;
SelectCommand!.CanExecuteValue = true;
SelectedIndex = -1;
}
});
}
private void UpdateList(string? path)
{
bool candidateFound = false;
var result = _suggestions.ProvideFileSystemSuggestions(path);
InputEntry? first = result.FirstOrDefault();
if (first != null)
{
if (new FileInfo(path!).FullName == new FileInfo(first.Value).FullName)
{
candidateFound = true;
}
}
if (!string.IsNullOrEmpty(path))
{
result = result.Prepend(new InputEntry(InputEntryType.Text, path!, path!));
}
Items = result;
if (candidateFound)
{
SelectedIndex = 1;
}
}
}
internal class BigInputViewModel : InputBarViewModel
{
public override IEnumerable<InputEntry>? Items
{
get => base.Items;
set => base.Items = value;
}
public override InputEntry? SelectedItem
{
get => base.SelectedItem;
set
{
base.SelectedItem = value;
SelectCommand.CanExecuteValue = value != null;
}
}
public override string? Text
{
get => base.Text;
set
{
base.Text = value;
if (value != SelectedItem?.ToString())
{
if (AllManifests != null)
{
UpdateList(value);
}
}
}
}
private InputEntry? _lastProcessedManifest;
public InputEntry? LastProcessedManifest
{
get => _lastProcessedManifest;
set => SetField(ref _lastProcessedManifest, value);
}
private IEnumerable<InputEntry>? _allManifests;
public IEnumerable<InputEntry>? AllManifests
{
get => _allManifests;
set
{
SetField(ref _allManifests, value);
Items = value;
SelectedItem = null;
LastProcessedManifest = null;
Text = null;
}
}
public Command<ViewModel> SelectCommand { get; }
public BigInputViewModel(Controller controller)
{
SelectCommand = new Command<ViewModel>(async viewModel =>
{
var mainInput = viewModel.MainInput;
try
{
SelectCommand!.CanExecuteValue = false;
mainInput.BrowseCommand.CanExecuteValue = false;
mainInput.SelectCommand.CanExecuteValue = false;
LastProcessedManifest = SelectedItem;
await controller.ProcessManifest(SelectedItem!.Value).ConfigureAwait(true);
}
catch (Exception exception)
{
CopyableBox.ShowDialog("SAGE FastHash 计算器的错误", _ =>
{
return Task.FromResult($"在加载 manifest 时发生错误:{exception}");
});
}
finally
{
SelectCommand!.CanExecuteValue = true;
mainInput.BrowseCommand.CanExecuteValue = true;
mainInput.SelectCommand.CanExecuteValue = true;
}
})
{
CanExecuteValue = false
};
}
private void UpdateList(string? input)
{
input ??= string.Empty;
input = input.Replace(VirtualFileSystem.AltDirectorySeparatorChar, VirtualFileSystem.DirectorySeparatorChar);
var filtered = from manifest in AllManifests
where manifest.Value.IndexOf(input, StringComparison.OrdinalIgnoreCase) != -1
select manifest;
Items = filtered;
if (Items.FirstOrDefault()?.Value.Equals(input, StringComparison.OrdinalIgnoreCase) == true)
{
SelectedIndex = 0;
}
}
}
internal class ObservableSortedCollection<T> : ObservableCollection<T> where T : IComparable<T>
{
private readonly HashSet<T> _set = new HashSet<T>();
public void SortedAddItems(IEnumerable<T> items)
{
try
{
items = from item in items
where !_set.Contains(item)
select item;
foreach (var item in items)
{
_set.Add(item);
base.Insert(BinarySearch(item), item);
}
}
catch
{
_set.Clear();
base.Clear();
throw;
}
}
public void SortedRemoveItems(Func<T, bool> ifRemove)
{
try
{
foreach (var i in Enumerable.Range(0, base.Count).Reverse())
{
var item = base[i];
if (ifRemove(item))
{
_set.Remove(item);
base.RemoveAt(i);
}
}
}
catch
{
_set.Clear();
base.Clear();
throw;
}
}
private int BinarySearch(T entry)
{
var begin = 0;
var end = base.Count;
while (begin < end)
{
var middle = begin + (end - begin) / 2;
var result = entry.CompareTo(base[middle]);
if (result == 0)
{
return middle;
}
else if (result < 0)
{
end = middle;
}
else if (result > 0)
{
begin = middle + 1;
}
}
return end;
}
}
}