HashCalculator.GUI/ViewModel.cs
2020-04-03 04:48:57 +02:00

572 lines
17 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.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Win32;
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);
}
#endregion
}
[SuppressMessage("Microsoft.Performance", "CA1812")]
internal class ViewModel : NotifyPropertyChanged
{
private static readonly Random _random = new Random();
public Action<Action<Action>> OnStartTraceListener { get; }
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);
}
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 = SuggestionString("不知道该显示些什么呢……");
public string StatusText
{
get => _statusText;
set => SetField(ref _statusText, value);
}
private bool _isXml;
public bool IsXml
{
get => _isXml;
set => SetField(ref _isXml, value);
}
private bool _isLoadingXml;
public bool IsLoadingXml
{
get => _isLoadingXml;
set => SetField(ref _isLoadingXml, value);
}
public Command CancelXml { get; }
[SuppressMessage("Microsoft.Performance", "CA1822")]
public string LoadCsfText => $"{(Translator.HasProvider ? "" : "")} CSF";
public Command<MainWindow> LoadCsf { get; }
private string _traceText = string.Empty;
public string TraceText
{
get => _traceText;
set
{
SetField(ref _traceText, value);
SuggestClearFilter = TraceText.Length > 32768;
}
}
private bool _suggestClearFilter;
public bool SuggestClearFilter
{
get => _suggestClearFilter;
set => SetField(ref _suggestClearFilter, value);
}
public Command ClearTraceText { get; }
public ViewModel()
{
var controller = new Controller(this);
OnStartTraceListener = controller.StartTrace;
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 dialog = new OpenFileDialog
{
Multiselect = false,
ValidateNames = true,
Filter = "CSF / BIG 文件|*.csf;*.big"
};
if (dialog.ShowDialog(window) == true)
{
await Controller.LoadCsf(dialog.FileName).ConfigureAwait(true);
}
}
finally
{
LoadCsf.CanExecuteValue = true;
}
});
FilterDisplayEntries = new Command(() =>
{
controller.UpdateEntries();
return Task.CompletedTask;
});
ClearTraceText = new Command(() =>
{
TraceText = string.Empty;
return Task.CompletedTask;
});
}
public static string SuggestionString(string original)
{
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 original;
}
public void StartTracerListener(Action<Action> action)
{
OnStartTraceListener(action);
}
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.IndexOf(chunk, StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
if (entry.InstanceIdString.IndexOf(chunk, StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
if (Translator.HasProvider)
{
if (entry.LocalizedNames.IndexOf(chunk, StringComparison.CurrentCultureIgnoreCase) != -1)
{
return true;
}
}
}
return false;
}
}
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>(window =>
{
var dialog = new OpenFileDialog
{
Multiselect = false,
ValidateNames = true
};
if (dialog.ShowDialog(window) == true)
{
Text = dialog.FileName;
}
return Task.CompletedTask;
});
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; }
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
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;
}
}
}