Changeset - a5faa82faf6a
[Not reviewed]
default
0 6 0
Tess Snider (Malkyne) - 3 years ago 2021-07-21 08:10:50
this@malkyne.org
Import and Export should be working correctly. Can now import Practical Paint
reactions.txt files. Fixed a crash taht occurred when switching profiles.
6 files changed with 135 insertions and 35 deletions:
0 comments (0 inline, 0 general)
App.axaml.cs
Show inline comments
...
 
@@ -16,32 +16,41 @@ namespace DesertPaintCodex
 
        public override void Initialize()
 
        {
 
            AvaloniaXamlLoader.Load(this);
 
        }
 

	
 
        public override void OnFrameworkInitializationCompleted()
 
        { 
 
            ShowWelcomeView();
 
            base.OnFrameworkInitializationCompleted();
 
        }
 

	
 
        public void ReturnToWelcome()
 
        {
 
            ShowWelcomeView();
 
            CloseMainWindow();
 
        }
 
        
 
        public void RefreshMainWindow()
 
        {
 
            if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return;
 
            
 
            desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
 
            CloseMainWindow();
 
            ShowMainWindow();
 
        }
 

	
 
        private void ShowWelcomeView()
 
        {
 
            if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return;
 
            
 
            desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
 
            _welcomeView         = new WelcomeView();
 
            _welcomeView.Closed += OnWelcomeViewClosed;
 
            _welcomeView.Show();
 
        }
 

	
 
        private void ShowMainWindow()
 
        {
 
            if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return;
 
            
 
            desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
Models/PlayerProfile.cs
Show inline comments
 
using System;
 
using System.IO;
 
using System.IO.Compression;
 
using System.Collections.Generic;
 
using System.Diagnostics;
 
using System.Text.RegularExpressions;
 
using DesertPaintCodex.Util;
 
using DesertPaintCodex.Services;
 

	
 
namespace DesertPaintCodex.Models
 
{
 
    public class PlayerProfile
 
    {
 
        private const string PaintRecipeFile = "dp_recipes.txt";
 
        private const string RibbonRecipeFile = "dp_ribbons.txt";
 
        
 
        private readonly string _reactFile;
 
        private readonly string _settingsFile;
 
        private readonly string _clipFile;
 

	
 
        private static readonly Regex _recipeHeaderRegex     = new(@"^--- Recipe: (?<colorname>(\w*\s)*\w+)\s*");
...
 
@@ -114,39 +115,39 @@ namespace DesertPaintCodex.Models
 
        private static void WriteReaction(TextWriter writer, string reagent1, string reagent2, string r, string g, string b)
 
        {
 
            writer.Write(reagent1);
 
            writer.Write(" ");
 
            writer.Write(reagent2);
 
            writer.Write(" ");
 
            writer.Write(r);
 
            writer.Write(" ");
 
            writer.Write(g);
 
            writer.Write(" ");
 
            writer.WriteLine(b);
 
        }
 

	
 
        public static void ConvertFromPP(string ppFile, string dpFile)
 
        {
 
            using StreamReader reader = new(ppFile);
 
            using StreamWriter writer = new(dpFile);
 
            using StreamWriter writer = new(dpFile, false);
 
            string?            line;
 
            while ((line = reader.ReadLine()) != null)
 
            {
 
                string[] tokens = line.Split('|');
 
                //if ((tokens.Length > 0) && (tokens [0] != "//"))
 
                if ((tokens.Length != 5) && (tokens[0].Trim() != "//"))
 
                if ((tokens.Length == 5) && (tokens[0].Trim() != "//"))
 
                {
 
                    string reagent1  = tokens[0].Trim();
 
                    string reagent2  = tokens[1].Trim();
 
                    string colorCode = tokens[2].Trim();
 
                    string change1   = tokens[3].Trim();
 
                    string change2   = tokens[4].Trim();
 
                    // Write reaction.
 
                    switch (colorCode)
 
                    {
 
                        case "W":
 
                            WriteReaction(writer, reagent1, reagent2, change1, change1, change1);
 
                            WriteReaction(writer, reagent2, reagent1, change2, change2, change2);
 
                            break;
 
                        case "R":
 
                            WriteReaction(writer, reagent1, reagent2, change1, "0", "0");
 
                            WriteReaction(writer, reagent2, reagent1, change2, "0", "0");
...
 
@@ -226,55 +227,59 @@ namespace DesertPaintCodex.Models
 
                    Reagent reagent2 = ReagentService.GetReagent(reagentName2);
 
                    reaction1 = Reactions.Find(reagent1, reagent2);
 
                    if (reaction1 != null)
 
                    {
 
                        reaction1.Exported = false;
 
                    }
 
                    reaction2 = Reactions.Find(reagent2, reagent1);
 
                    if (reaction2 != null)
 
                    {
 
                        reaction2.Exported = false;
 
                    }
 
                }
 
            }
 
            return true;
 
        }
 

	
 
        public void ImportFromPP(string importDir)
 
        public void ImportFromPP(string reactionsFile)
 
        {
 
            // Convert old file.
 
            ConvertFromPP(
 
                Path.Combine(importDir, "reactions.txt"),
 
                _reactFile);
 
            ConvertFromPP(reactionsFile, _reactFile);
 
            try
 
            {
 
                // If there is an ingredients file, move it in.
 
                string importDir = Path.GetDirectoryName(reactionsFile) ?? "";
 
                File.Copy(
 
                    Path.Combine(importDir, "ingredients.txt"),
 
                    Path.Combine(Directory, "ingredients.txt"),
 
                    true);
 
            }
 
            catch (Exception)
 
            {
 
                // If there is no ingredients file, we don't really care.	
 
            }
 
        }
 

	
 
        public void Import(string file)
 
        {
 
            ZipFile.ExtractToDirectory(file, Directory);
 
            if (!File.Exists(file))
 
            {
 
                Debug.WriteLine("Import file does not exist: " + file);
 
                // TODO: Show message dialog.
 
            }
 
            ZipFile.ExtractToDirectory(file, Directory, true);
 
        }
 

	
 
        public void Export(string file)
 
        {
 
            ZipFile.CreateFromDirectory(Directory, file);
 
        }
 

	
 
        public bool Load()
 
        {
 
            string? line;
 
            ProfileSettings.Reset();
 
            ProfileSettings.Load(_settingsFile);
 
            Reactions.Clear();
 
            if (File.Exists(ReagentFile))
 
            {
 
                ReagentService.LoadProfileReagents(ReagentFile);
Services/PaletteService.cs
Show inline comments
...
 
@@ -11,32 +11,34 @@ namespace DesertPaintCodex.Services
 
    {
 
        private static readonly Regex ColorEntry = new(@"\#(?<red>\w\w)(?<green>\w\w)(?<blue>\w\w)\s*(?<name>\w+)");
 

	
 
        public static List<PaintColor> Colors { get; } = new();
 
        
 
        private static bool _initialized = false;
 
        
 
        
 
        public static void Initialize()
 
        {
 
            if (_initialized) return;
 

	
 
            string? colorsPath = FileUtils.FindApplicationResourceFile("colors.txt");
 
            Debug.Assert(colorsPath != null);
 
    
 
            Load(colorsPath);
 

	
 
            _initialized = true;
 
        }
 

	
 
        public static void Load(string file)
 
        {
 
            using StreamReader reader = new StreamReader(file);
 
            string? line;
 
            while ((line = reader.ReadLine()) != null)
 
            {
 
                Match match = ColorEntry.Match(line);
 
                if (match.Success)
 
                {
 
                    Colors.Add(new PaintColor(match.Groups["name"].Value,
 
                        match.Groups["red"].Value,
 
                        match.Groups["green"].Value,
 
                        match.Groups["blue"].Value));
 
                }
Services/ProfileManager.cs
Show inline comments
...
 
@@ -38,32 +38,38 @@ namespace DesertPaintCodex.Services
 
                {
 
                    _profileList.Add(dir.Name);
 
                }
 
            }
 

	
 
            _areProfilesLoaded = true;
 
            return _profileList;
 
        }
 

	
 
        public static PlayerProfile LoadProfile(string name)
 
        {
 
            CurrentProfile = new PlayerProfile(name, Path.Combine(FileUtils.AppDataPath, name));
 
            CurrentProfile.Load();
 
            return CurrentProfile;
 
        }
 

	
 
        public static void ReloadProfile()
 
        {
 
            if (CurrentProfile == null) return;
 
            LoadProfile(CurrentProfile.Name);
 
        }
 

	
 
        public static PlayerProfile CreateNewProfile(string name)
 
        {
 
            CurrentProfile = new PlayerProfile(name, Path.Combine(FileUtils.AppDataPath, name));
 
            CurrentProfile.Initialize();
 
            // Invalidate profile list, so it will reload next time.
 
            _profileList.Clear();
 
            _areProfilesLoaded = false;
 
            return CurrentProfile;
 
        }
 

	
 
        public static int GetProfileCount()
 
        {
 
            // This is a function instead of a property, because it may be slow.
 
            List<string> profiles = GetProfileList();
 
            return profiles.Count;
 
        }
ViewModels/MainWindowViewModel.cs
Show inline comments
 
using System.Collections.Generic;
 
using System;
 
using System.Collections.Generic;
 
using System.Diagnostics;
 
using System.IO;
 
using System.Reactive;
 
using System.Reactive.Linq;
 
using System.Threading.Tasks;
 
using Avalonia;
 
using Avalonia.Controls;
 
using Avalonia.Controls.ApplicationLifetimes;
 
using Avalonia.Input.Platform;
 
using DesertPaintCodex.Models;
 
using DesertPaintCodex.Services;
 
using ReactiveUI;
 

	
 
namespace DesertPaintCodex.ViewModels
 
{
 
    public class MainWindowViewModel : ViewModelBase
 
    {
 
        private string _statusText = string.Empty;
 
        public string StatusText { get => _statusText; private set => this.RaiseAndSetIfChanged(ref _statusText, value); }
 

	
 
        public string StatusText
 
        {
 
            get => _statusText;
 
            private set => this.RaiseAndSetIfChanged(ref _statusText, value);
 
        }
 

	
 
        private static readonly List<string> ZipFileExtensions = new() {"zip"};
 
        private static readonly FileDialogFilter ZipFileFilter = new() {Extensions = ZipFileExtensions, Name = "Zip"};
 
        private static readonly List<FileDialogFilter> ZipFileFilters = new() {ZipFileFilter};
 

	
 
        private static readonly List<string> TxtFileExtensions = new() {"txt"};
 
        private static readonly FileDialogFilter TxtFileFilter = new() {Extensions = TxtFileExtensions, Name = "Text"};
 
        private static readonly List<FileDialogFilter> TxtFileFilters = new() {TxtFileFilter};
 
        
 
        private static readonly List<string> ZipFileExtensions = new() { $"*.zip;" };
 
        private static readonly FileDialogFilter ZipDialogFilter = new() {Extensions = ZipFileExtensions};
 
        private static readonly List<FileDialogFilter> ZipDialogFilters = new() { ZipDialogFilter };
 
        private static readonly List<FileDialogFilter> NoFilters = new();
 
        private static readonly List<FileDialogFilter> NoFileFilters = new();
 

	
 
        public MainWindowViewModel()
 
        {
 
            if (!ProfileManager.HasProfileLoaded && ProfileManager.HasProfiles())
 
            {
 
                ProfileManager.LoadProfile(ProfileManager.GetProfileList()[0]);
 
            }
 
            Debug.Assert(ProfileManager.HasProfileLoaded);
 
            
 
            PaletteService.Initialize();
 
            ReactionTestService.Initialize();
 
            
 
            ShowAboutDialog = new Interaction<AboutViewModel, Unit>();
 
            ShowScreenSettingsDialog = new Interaction<ScreenSettingsViewModel, Unit>();
 
            
 
            StatusText = "USER PROFILE: " + ProfileManager.CurrentProfile?.Name;
 
            Exit = ReactiveCommand.Create(() => { });
 
        }
 

	
 
        public async void ManageProfiles()
 
        {
 
            if (Application.Current is not App app) return;
 
            
 
            if (await ValidateSafeExit())
 
            {
 
                ProfileManager.UnloadProfile();
 
                app.ReturnToWelcome();
 
            }
 
        }
 

	
 
        public static async void ImportProfile()
 
        public async void ImportProfile()
 
        {
 
            string? fileName = await GetLoadFileName("Open Zipped Profile", ZipDialogFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            string? fileName = await GetLoadFileName("Open Zipped Profile", ZipFileFilters);
 
            
 
            if (string.IsNullOrEmpty(fileName)) return;
 

	
 
            try
 
            {
 
                ProfileManager.CurrentProfile?.Import(fileName);
 
            }
 
            catch (Exception e)
 
            {
 
                Debug.WriteLine("ImportProfile threw exception " + e);
 
                await ShowMessageBox("Import Failed",
 
                    "Your file could not be imported. It must be a zip file containing a Desert Paint Codex profile.", "OK");
 
            }
 

	
 
            ProfileManager.ReloadProfile();
 
            
 
            if (Application.Current is not App app) return;
 
            
 
            app.RefreshMainWindow();
 
        }
 

	
 
        public async void ExportProfile()
 
        {
 
            string? fileName = await GetSaveFileName("Save Zipped Profile", ZipFileFilters);
 
            
 
            if (string.IsNullOrEmpty(fileName)) return;
 
            
 
            try
 
            {
 
                ProfileManager.CurrentProfile?.Export(fileName);
 
            }
 
            catch (Exception e)
 
            {
 
                Debug.WriteLine("ExportProfile threw exception " + e);
 
                await ShowMessageBox("Export Failed",
 
                    "Your profile could not be exported. Please ensure that you are providing a valid filename for the zip file that we are creating.", "OK");
 
            }
 
        }
 

	
 
        public static async void ExportProfile()
 
        public async void ExportForPP(object f)
 
        {
 
            string? fileName = await GetSaveFileName("Save Zipped Profile", ZipDialogFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            string? fileName = await GetSaveFileName("Save Practical Paint File", TxtFileFilters, "reactions.txt");
 
            
 
            if (string.IsNullOrEmpty(fileName)) return;
 
            
 
            try
 
            {
 
                ProfileManager.CurrentProfile?.Import(fileName);
 
                ProfileManager.CurrentProfile?.SaveToPP(fileName);
 
            }
 
            catch (Exception e)
 
            {
 
                Debug.WriteLine("ExportForPP threw exception " + e);
 
                await ShowMessageBox("Export Failed",
 
                    "Please ensure that you have provided a valid file path for your reactions file.", "OK");
 
            }
 
        }
 

	
 
        public static async void ExportForPP()
 
        public async void ImportFromPP()
 
        {
 
            string? fileName = await GetSaveFileName("Save Practical Paint File", NoFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            string? fileName = await GetLoadFileName("Import Reactions File", TxtFileFilters, "reactions.txt");
 
            
 
            if (string.IsNullOrEmpty(fileName)) return;
 

	
 
            try
 
            {
 
                ProfileManager.CurrentProfile?.ImportFromPP(fileName);
 
            }
 
            catch (Exception e)
 
            {
 
                ProfileManager.CurrentProfile?.SaveToPP(fileName);
 
                Debug.WriteLine("ImportFromPP threw exception " + e);
 
                await ShowMessageBox("Import Failed",
 
                    "Your file could not be imported. It must be a valid Practical Paint reactions.txt file.", "OK");
 
            }
 

	
 
            ProfileManager.ReloadProfile();
 
            
 
            if (Application.Current is not App app) return;
 
            
 
            app.RefreshMainWindow();
 
        }
 

	
 
        public static async void ExportPaintRecipes()
 
        {
 
            string? fileName = await GetSaveFileName("Export Paint Recipes", NoFilters);
 
            string? fileName = await GetSaveFileName("Export Paint Recipes", NoFileFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            {
 
                ProfileManager.CurrentProfile?.ExportWikiRecipes(fileName);
 
            }
 
        }
 
        
 
        public static async void ExportRibbonRecipes()
 
        {
 
            string? fileName = await GetSaveFileName("Export Ribbon Recipes", NoFilters);
 
            string? fileName = await GetSaveFileName("Export Ribbon Recipes", NoFileFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            {
 
                ProfileManager.CurrentProfile?.ExportWikiRibbons(fileName);
 
            }
 
        }
 

	
 
        public static async void CopyPaintRecipes()
 
        {
 
            StringWriter writer = new();
 
            ProfileManager.CurrentProfile?.ExportWikiRecipes(writer);
 
            IClipboard clipboard = Application.Current.Clipboard;
 
            await writer.FlushAsync();
 
            await clipboard.SetTextAsync(writer.ToString());
 
            writer.Close();
 
        }
 
        
...
 
@@ -123,55 +191,57 @@ namespace DesertPaintCodex.ViewModels
 
            await ShowScreenSettingsDialog.Handle(new ScreenSettingsViewModel());
 
        }
 

	
 
        public async Task ShowAbout()
 
        {
 
            await ShowAboutDialog.Handle(new AboutViewModel());
 
        }
 

	
 
        public async Task<bool> ValidateSafeExit()
 
        {
 
            // TODO: Determine if there's unsaved stuff we need to deal with.
 
            // return await ShowYesNoBox("Leaving so Soon?", "[A potential reason not to quit goes here]");
 
            await Task.Delay(1); // Stub to prevent warnings.
 
            return true;
 
        }
 

	
 
        private static async Task<string?> GetLoadFileName(string title, List<FileDialogFilter> filters)
 
        private static async Task<string?> GetLoadFileName(string title, List<FileDialogFilter> filters, string? fileName = null)
 
        {
 
            if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return null;
 
            
 
            // TODO: Figure out why the file filters aren't working.
 
            
 
            OpenFileDialog dialog = new()
 
            {
 
                Title         = title,
 
                Filters       = NoFilters, // filters,
 
                AllowMultiple = false
 
                Title           = title,
 
                Filters         = filters,
 
                InitialFileName = fileName,
 
                AllowMultiple   = false
 
            };
 

	
 
            string[] files = await dialog.ShowAsync(desktop.MainWindow);
 
            return files.Length > 0 ? files[0] : null;
 
        }
 
        
 
        
 
        private static async Task<string?> GetSaveFileName(string title, List<FileDialogFilter> filters)
 
        private static async Task<string?> GetSaveFileName(string title, List<FileDialogFilter> filters, string? fileName = null)
 
        {
 
            if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return null;
 
            
 
            // TODO: Figure out why the file filters aren't working.
 
            
 
            SaveFileDialog dialog = new()
 
            {
 
                Title   = title,
 
                Filters = NoFilters, // filters
 
                Title           = title,
 
                Filters         = filters,
 
                InitialFileName = fileName
 
            };
 

	
 
            return await dialog.ShowAsync(desktop.MainWindow);
 
        }
 
        
 
        public Interaction<AboutViewModel, Unit> ShowAboutDialog { get; }
 
        public Interaction<ScreenSettingsViewModel, Unit> ShowScreenSettingsDialog { get; }
 

	
 
        public ReactiveCommand<Unit, Unit> Exit { get; }
 
    }
 
}
Views/MainWindow.axaml
Show inline comments
...
 
@@ -48,60 +48,68 @@
 

	
 
        <Style Selector="TabControl.ActivityPicker > TabItem:selected">
 
            <Setter Property="Background" Value="{DynamicResource FlatBackgroundBrush}"/>
 
            <Setter Property="Foreground" Value="#FFFFFF"/>
 
        </Style>
 

	
 
        <Style Selector="TabControl.ActivityPicker > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
 
            <Setter Property="Background" Value="{DynamicResource FlagBackgroundBrush}"/>
 
        </Style>
 
    </Window.Styles>
 
    <Grid ColumnDefinitions="*" RowDefinitions="*">
 
        <DockPanel Name="Main" Grid.Row="0" Grid.Column="0">
 
            <Menu DockPanel.Dock="Top" Margin="0, 5">
 
                <MenuItem Header="_File">
 
                    <MenuItem Header="Profile">
 
                        <MenuItem Header="Manage Profiles..." Command="{Binding ManageProfiles}"></MenuItem>
 
                        <Separator/>
 
                        <MenuItem Header="Import Profile..." Command="{Binding ImportProfile}">
 
                            <ToolTip.Tip>
 
                                Will overwrite the current profile with a profile from a zipped folder.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <MenuItem Header="Export Profile..." Command="{Binding ExportProfile}">
 
                            <ToolTip.Tip>
 
                                Will export the current profile to a zipped folder.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <MenuItem Header="Export for PracticalPaint..." Command="{Binding ExportForPP}">
 
                        <Separator/>
 
                        <MenuItem Header="Import PracticalPaint Reactions..." Command="{Binding ImportFromPP}">
 
                            <ToolTip.Tip>
 
                                Will import a Practical Paint reactions file, replacing this profile's reactions.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <MenuItem Header="Export PracticalPaint Reactions..." Command="{Binding ExportForPP}">
 
                            <ToolTip.Tip>
 
                                Will generate a Practical Paint reactions file from the current profile.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                    </MenuItem>
 

	
 
                    <Separator/>
 
                    <MenuItem Header="Recipes">
 
                        <MenuItem Header="Export Paint Recipes..." Command="{Binding ExportPaintRecipes}">
 
                            <ToolTip.Tip>
 
                                Exports recipes in Wiki table format.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <MenuItem Header="Export Ribbon Recipes..." Command="{Binding ExportRibbonRecipes}">
 
                            <ToolTip.Tip>
 
                                Exports recipes in Wiki table format.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <Separator/>
 
                        <MenuItem Header="Copy Paint Recipes to Clipboard" Command="{Binding CopyPaintRecipes}">
 
                            <ToolTip.Tip>
 
                                Copies recipes in Wiki table format.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <MenuItem Header="Copy Ribbon Recipes to Clipboard" Command="{Binding CopyRibbonRecipes}">
 
                            <ToolTip.Tip>
 
                                Copies recipes in Wiki table format.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                    </MenuItem>
 

	
 
                    <Separator/>
 

	
 
                    <MenuItem Header="Screen Settings..." Command="{Binding ShowScreenSettings}"></MenuItem>
 
                    <Separator/>
0 comments (0 inline, 0 general)