diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index 07d6873c..cc8b523c 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -6,14 +6,14 @@ using ICSharpCode.AvalonEdit.Document; namespace FModel.Extensions; -public static class StringExtensions +public static partial class StringExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetReadableSize(double size) { if (size == 0) return "0 B"; - string[] sizes = { "B", "KB", "MB", "GB", "TB" }; + string[] sizes = ["B", "KB", "MB", "GB", "TB"]; var order = 0; while (size >= 1024 && order < sizes.Length - 1) { @@ -27,21 +27,22 @@ public static class StringExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetNameLineNumber(this string s, string lineToFind) { + if (KismetRegex().IsMatch(lineToFind)) + return s.GetKismetLineNumber(lineToFind); if (int.TryParse(lineToFind, out var index)) return s.GetLineNumber(index); lineToFind = $" \"Name\": \"{lineToFind}\","; using var reader = new StringReader(s); var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) { lineNum++; if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) return lineNum; } - return 1; + return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -64,18 +65,17 @@ public static class StringExtensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetKismetLineNumber(this string s, string input) + private static int GetKismetLineNumber(this string s, string input) { - var match = Regex.Match(input, @"^(.+)\[(\d+)\]$"); + var match = KismetRegex().Match(input); var name = match.Groups[1].Value; int index = int.Parse(match.Groups[2].Value); var lineToFind = $" \"Name\": \"{name}\","; var offset = $"\"StatementIndex\": {index}"; using var reader = new StringReader(s); var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) { lineNum++; if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) @@ -91,7 +91,7 @@ public static class StringExtensions } } - return 1; + return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -99,8 +99,7 @@ public static class StringExtensions { using var reader = new StringReader(s); var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) { lineNum++; if (line.Equals(" {")) @@ -110,6 +109,9 @@ public static class StringExtensions return lineNum + 1; } - return 1; + return -1; } + + [GeneratedRegex(@"^(.+)\[(\d+)\]$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex KismetRegex(); } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index fdcc086d..63a66d55 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -907,7 +907,10 @@ public class CUE4ParseViewModel : ViewModel { var package = Provider.LoadPackage(entry); - TabControl.AddTab($"{entry.Name} (Metadata)"); + if (TabControl.CanAddTabs) TabControl.AddTab(entry); + else TabControl.SelectedTab.SoftReset(entry); + + TabControl.SelectedTab.TitleExtra = "Metadata"; TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false); diff --git a/FModel/ViewModels/Commands/ImageCommand.cs b/FModel/ViewModels/Commands/ImageCommand.cs index 264255ac..0201c989 100644 --- a/FModel/ViewModels/Commands/ImageCommand.cs +++ b/FModel/ViewModels/Commands/ImageCommand.cs @@ -14,34 +14,34 @@ public class ImageCommand : ViewModelCommand { } - public override void Execute(TabItem contextViewModel, object parameter) + public override void Execute(TabItem tabViewModel, object parameter) { - if (parameter == null || !contextViewModel.HasImage) return; + if (parameter == null || !tabViewModel.HasImage) return; switch (parameter) { case "Open": { - Helper.OpenWindow(contextViewModel.SelectedImage.ExportName + " (Image)", () => + Helper.OpenWindow(tabViewModel.SelectedImage.ExportName + " (Image)", () => { var popout = new ImagePopout { - Title = contextViewModel.SelectedImage.ExportName + " (Image)", - Width = contextViewModel.SelectedImage.Image.Width, - Height = contextViewModel.SelectedImage.Image.Height, - WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, - ImageCtrl = { Source = contextViewModel.SelectedImage.Image } + Title = tabViewModel.SelectedImage.ExportName + " (Image)", + Width = tabViewModel.SelectedImage.Image.Width, + Height = tabViewModel.SelectedImage.Image.Height, + WindowState = tabViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, + ImageCtrl = { Source = tabViewModel.SelectedImage.Image } }; - RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor)); + RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(tabViewModel.SelectedImage.RenderNearestNeighbor)); popout.Show(); }); break; } case "Copy": - ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png"); + ClipboardExtensions.SetImage(tabViewModel.SelectedImage.ImageBuffer, $"{tabViewModel.SelectedImage.ExportName}.png"); break; case "Save": - contextViewModel.SaveImage(); + tabViewModel.SaveImage(); break; } } diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index 5abbc4e3..a7911782 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -15,7 +15,7 @@ public class TabCommand : ViewModelCommand { } - public override async void Execute(TabItem contextViewModel, object parameter) + public override async void Execute(TabItem tabViewModel, object parameter) { switch (parameter) { @@ -23,53 +23,53 @@ public class TabCommand : ViewModelCommand _applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick); break; case "Close_Tab": - _applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel); + _applicationView.CUE4Parse.TabControl.RemoveTab(tabViewModel); break; case "Close_All_Tabs": _applicationView.CUE4Parse.TabControl.RemoveAllTabs(); break; case "Close_Other_Tabs": - _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel); + _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel); break; case "Asset_Export_Data": - await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Entry)); + await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry)); break; case "Asset_Save_Properties": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Properties); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Properties); }); break; case "Asset_Save_Textures": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Textures); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Textures); }); break; case "Asset_Save_Models": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Meshes); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes); }); break; case "Asset_Save_Animations": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Animations); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations); }); break; case "Open_Properties": - if (contextViewModel.Entry.Name == "New Tab" || contextViewModel.Document == null) return; - Helper.OpenWindow(contextViewModel.Entry.Name + " (Properties)", () => + if (tabViewModel.Header == "New Tab" || tabViewModel.Document == null) return; + Helper.OpenWindow(tabViewModel.Header + " (Properties)", () => { - new PropertiesPopout(contextViewModel) + new PropertiesPopout(tabViewModel) { - Title = contextViewModel.Entry.Name + " (Properties)" + Title = tabViewModel.Header + " (Properties)" }.Show(); }); break; case "Copy_Asset_Path": - Clipboard.SetText(contextViewModel.Entry.Path); + Clipboard.SetText(tabViewModel.Entry.Path); break; } } diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 1eda1e1c..39edaced 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -97,7 +97,22 @@ public class TabItem : ViewModel public GameFile Entry { get => _entry; - set => SetProperty(ref _entry, value); + set + { + SetProperty(ref _entry, value); + RaisePropertyChanged(nameof(Header)); + } + } + + private string _titleExtra; + public string TitleExtra + { + get => _titleExtra; + set + { + SetProperty(ref _titleExtra, value); + RaisePropertyChanged(nameof(Header)); + } } private bool _hasSearchOpen; @@ -194,6 +209,8 @@ public class TabItem : ViewModel } } + public string Header => $"{Entry.Name}{(string.IsNullOrEmpty(TitleExtra) ? "" : $" ({TitleExtra})")}"; + public bool HasImage => SelectedImage != null; public bool HasMultipleImages => _images.Count > 1; public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}"; @@ -219,6 +236,7 @@ public class TabItem : ViewModel public void SoftReset(GameFile entry) { Entry = entry; + TitleExtra = string.Empty; ParentExportType = string.Empty; ScrollTrigger = null; Application.Current.Dispatcher.Invoke(() => @@ -389,7 +407,7 @@ public class TabControlViewModel : ViewModel public void AddTab(string title) => AddTab(new FakeGameFile(title)); public void AddTab(GameFile entry, string parentExportType = null) { - if (SelectedTab?.Entry.Name == "New Tab") + if (SelectedTab?.Header == "New Tab") { SelectedTab.Entry = entry; return; diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index 86fdb4d4..d642a47e 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -76,28 +76,18 @@ public class GamePathVisualLineText : VisualLineText if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase) && !a.ParentVisualLine.Document.GetText(firstLine.Offset, firstLine.Length).Equals(" \"Summary\": {")) // Show Metadata case { - int lineNumber; - DocumentLine line; - - if (Regex.IsMatch(obj, @"^(.+)\[(\d+)\]$")) + var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj); + if (lineNumber > -1) { - lineNumber = a.ParentVisualLine.Document.Text.GetKismetLineNumber(obj); - line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); - } - else - { - lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj); - line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); + AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); + return; } + } - AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); - AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); - } - else - { - await _threadWorkerView.Begin(cancellationToken => - _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType)); - } + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType)); }; return a; } diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index bc5fdab8..cdf50ae0 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -127,6 +127,8 @@ public partial class AvalonEditor if (!tabItem.ShouldScroll) return; var lineNumber = avalonEditor.Document.Text.GetNameLineNumber(tabItem.ScrollTrigger); + if (lineNumber == -1) lineNumber = 1; + var line = avalonEditor.Document.GetLineByNumber(lineNumber); avalonEditor.Select(line.Offset, line.Length); avalonEditor.ScrollToLine(lineNumber); diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 0a4af432..d7540235 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -651,7 +651,7 @@ - +