Changeset - 867b2b613117
[Not reviewed]
default
0 3 0
Jason Maltzen - 3 years ago 2021-08-16 07:53:33
jason@hiddenachievement.com
Add an export to paintmix option
3 files changed with 62 insertions and 2 deletions:
0 comments (0 inline, 0 general)
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*");
 
        private static readonly Regex _recipeIngredientRegex = new(@"(?<ingredient>(\w+\s)?\w+)\s*\|\s*(?<quantity>\d+)\s*");
 
        
 
        private Settings ProfileSettings { get; } = new();
 

	
 
        public string Directory { get; }
 

	
 
        public string Name { get; private set; }
 
        
 
        public ReactionSet Reactions { get; } = new();
 

	
 
        public Dictionary<string, Dictionary<string, ClipType>> Clippers { get; } = new();
 

	
 
        public string ReagentFile { get; }
 

	
 
        public Dictionary<string, PaintRecipe> Recipes { get; } = new();
 

	
 
        public Dictionary<string, PaintRecipe> RibbonRecipes { get; } = new();
 

	
 
        public int RecipeCount
 
        {
 
            get
 
            {
 
                int count = 0;
 
                foreach (PaintRecipe recipe in Recipes.Values)
 
                {
 
                    if (recipe.IsValidForConcentration(PaintRecipe.PaintRecipeMinConcentration))
 
                    {
 
                        ++count;
 
                    }
 
                }
 
                return count;
 
            }
 
        }
 

	
 
        public int RibbonCount
 
        {
 
            get
 
            {
 
                int count = 0;
 
                foreach (PaintRecipe recipe in RibbonRecipes.Values)
 
                {
 
                    if (recipe.IsValidForConcentration(PaintRecipe.RibbonRecipeMinConcentration))
 
                    {
 
                        ++count;
 
                    }
 
                }
 
                return count;
 
            }
 
        }
 
        
 
        public PlayerProfile(string name, string directory)
 
        {
 
            Name          = name;
 
            Directory     = directory;
 
            _reactFile    = Path.Combine(directory, "dp_reactions.txt");
 
            ReagentFile   = Path.Combine(directory, "ingredients.txt");
 
            _settingsFile = Path.Combine(directory, "settings");
 
            _clipFile     = Path.Combine(directory, "clips.txt");
 
            foreach (PaintColor color in PaletteService.Colors)
 
            {
 
                Recipes.Add(color.Name, new PaintRecipe());
 
            }
 
            foreach (PaintColor color in PaletteService.Colors)
 
            {
 
                RibbonRecipes.Add(color.Name, new PaintRecipe());
 
            }
 
        }
 

	
 
        public bool Initialize()
 
        {
 
            // Copy template files into new directory.
 
            string? templatePath = FileUtils.FindApplicationResourceDirectory("template");
 

	
 
            if (templatePath == null)
 
            {
 
                return false;
 
            }
 

	
 
            // Create new directory.
 
            System.IO.Directory.CreateDirectory(Directory);
 

	
 
            DirectoryInfo di = new(templatePath);
 
            FileInfo[] templateFiles = di.GetFiles();
 

	
 
            foreach (FileInfo file in templateFiles)
 
            {
 
                string destFile = Path.Combine(Directory, file.Name);
 
                File.Copy(file.FullName, destFile, true);
 
                if (!File.Exists(destFile)) return false;
 
            }
 
            return true;
 
        }
 

	
 
        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, 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() != "//"))
 
                {
 
                    string reagent1Name  = tokens[0].Trim();
 
                    string reagent2Name  = tokens[1].Trim();
 
                    string colorCode = tokens[2].Trim();
 
                    string change1   = tokens[3].Trim();
 
                    string change2   = tokens[4].Trim();
 

	
 
                    Reagent reagent1 = ReagentService.GetReagent(reagent1Name);
 
                    Reagent reagent2 = ReagentService.GetReagent(reagent2Name);
 

	
 
                    if (reagent1 == null || reagent2 == null) continue;
 

	
 
                    bool hasChange1 = int.TryParse(change1, out _);
 
                    bool hasChange2 = int.TryParse(change2, out _);
 

	
 
                    // Write reaction.
 
                    switch (colorCode)
 
                    {
 
                        case "W":
 
                            if (hasChange1)
 
                            {
 
                                WriteReaction(writer, reagent1Name, reagent2Name, change1, change1, change1);
 
                            }
 
                            if (hasChange2)
 
                            {
 
                                WriteReaction(writer, reagent2Name, reagent1Name, change2, change2, change2);
 
                            }
 
                            break;
 
                        case "R":
 
                            if (hasChange1)
 
                            {
 
                                WriteReaction(writer, reagent1Name, reagent2Name, change1, "0", "0");
 
                            }
 
                            if (hasChange2)
 
                            {
 
                                WriteReaction(writer, reagent2Name, reagent1Name, change2, "0", "0");
 
                            }
 
                            break;
 
                        case "G":
 
                            if (hasChange1)
 
                            {
 
                                WriteReaction(writer, reagent1Name, reagent2Name, "0", change1, "0");
 
                            }
 
                            if (hasChange2)
 
                            {
 
                                WriteReaction(writer, reagent2Name, reagent1Name, "0", change2, "0");
 
                            }
 
                            break;
 
                        case "B":
 
                            if (hasChange1)
 
                            {
 
                                WriteReaction(writer, reagent1Name, reagent2Name, "0", "0", change1);
 
                            }
 
                            if (hasChange2)
 
                            {
 
                                WriteReaction(writer, reagent2Name, reagent1Name, "0", "0", change2);
 
                            }
 
                            break;
 
                    }
 
                }
 
            }
 
        }
 

	
 
        public bool SaveToPP(string ppFile)
 
        {
 
            Reaction? reaction1, reaction2;
 
            using (StreamWriter writer = new(ppFile))
 
            {
 
                foreach (string reagentName1 in ReagentService.Names)
 
                {
 
                    // TODO: could be more efficient by only iterating over the names after reagent1
 
                    foreach (string reagentName2 in ReagentService.Names)
 
                    {
 
                        if (reagentName1.Equals(reagentName2)) continue;
 

	
 
                        Reagent reagent1 = ReagentService.GetReagent(reagentName1);
 
                        Reagent reagent2 = ReagentService.GetReagent(reagentName2);
 
                        reaction1 = Reactions.Find(reagent1, reagent2);
 
                        
 
                        if (reaction1 is not {Exported: false}) continue;
 
                        
 
                        reaction2 = Reactions.Find(reagent2, reagent1);
 
                        
 
                        if (reaction2 == null) continue;
 
                        
 
                        writer.Write(reagent1.PracticalPaintName + " | " + reagent2.PracticalPaintName + " | ");
 
                        if ((Math.Abs(reaction1.Red) > Math.Abs(reaction1.Green)) ||
 
                            (Math.Abs(reaction2.Red) > Math.Abs(reaction2.Green)))
 
                        {
 
                            writer.WriteLine("R | " + reaction1.Red + " | " + reaction2.Red);
 
                        }
 
                        else if ((Math.Abs(reaction1.Green) > Math.Abs(reaction1.Red)) ||
 
                            (Math.Abs(reaction2.Green) > Math.Abs(reaction2.Red)))
 
                        {
 
                            writer.WriteLine("G | " + reaction1.Green + " | " + reaction2.Green);
 
                        }
 
                        else if ((Math.Abs(reaction1.Blue) > Math.Abs(reaction1.Red)) ||
 
                            (Math.Abs(reaction2.Blue) > Math.Abs(reaction2.Red)))
 
                        {
 
                            writer.WriteLine("B | " + reaction1.Blue + " | " + reaction2.Blue);
 
                        }
 
                        else
 
                        {
 
                            writer.WriteLine("W | " + reaction1.Red + " | " + reaction2.Red);
 
                        }
 
                        reaction1.Exported = true;
 
                        reaction2.Exported = true;
 
                    }
 
                }
 
            }
 

	
 
            // Clear Exported flags.
 
            foreach (string reagentName1 in ReagentService.Names)
 
            {
 
                // TODO: could be more efficient by only iterating over the names after reagent1
 
                foreach (string reagentName2 in ReagentService.Names)
 
                {
 
                    if (reagentName1.Equals(reagentName2))
 
                    {
 
                        continue;
 
                    }
 
                    Reagent reagent1 = ReagentService.GetReagent(reagentName1);
 
                    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 reactionsFile)
 
        {
 
            // Convert old file.
 
            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)
 
        {
 
            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);
 
            }
 
            else
 
            {
 
                return false;
 
            }
 
            ReagentService.InitializeReactions(Reactions);
 
            if (!File.Exists(_reactFile))
 
            {
 
                return false;
 
            }
 
            using (StreamReader reader = new(_reactFile))
 
            {
 
                while ((line = reader.ReadLine()) != null)
 
                {
 
                    string[] tokens = line.Split(' ');
 
                    if (tokens.Length == 5)
 
                    {
 
                        Reagent reagent1 = ReagentService.GetReagent(tokens[0].Trim());
 
                        Reagent reagent2 = ReagentService.GetReagent(tokens[1].Trim());
 
                        Reaction reaction = new(
 
                            int.Parse(tokens[2].Trim()),
 
                            int.Parse(tokens[3].Trim()),
 
                            int.Parse(tokens[4].Trim())
 
                            );
 
                        Reactions.Set(reagent1, reagent2, reaction);
 
                    }
 
                }
 
            }
 

	
 
            if (!File.Exists(_clipFile)) return true;
 
            {
 
                using StreamReader reader = new(_clipFile);
 
                while ((line = reader.ReadLine()) != null)
 
                {
 
                    string[] tokens = line.Split(' ');
 
                    
 
                    if (tokens.Length != 3) continue;
 
                    
 
                    string reagent1 = tokens[0].Trim();
 
                    if (!Clippers.ContainsKey(reagent1))
 
                    {
 
                        Clippers.Add(reagent1, new Dictionary<string, ClipType>());
 
                    }
 
                    Clippers[reagent1][tokens[1].Trim()] = (ClipType)int.Parse(tokens[2].Trim());
 
                }
 
            }
 

	
 
            return true;
 
        }
 

	
 
        public void Save()
 
        {
 
            ProfileSettings.Save(_settingsFile);
 
            Reaction? reaction;
 
            using (StreamWriter writer = new(_reactFile, false))
 
            {
 
                foreach (string reagentName1 in ReagentService.Names)
 
                {
 
                    // TODO: could be more efficient by only iterating over the names after reagent1
 
                    foreach (string reagentName2 in ReagentService.Names)
 
                    {
 
                        if (reagentName1.Equals(reagentName2))
 
                        {
 
                            continue;
 
                        }
 
                        Reagent reagent1 = ReagentService.GetReagent(reagentName1);
 
                        Reagent reagent2 = ReagentService.GetReagent(reagentName2);
 
                        reaction = Reactions.Find(reagent1, reagent2);
 
                        if (reaction != null)
 
                        {
 
                            writer.WriteLine(reagent1.PracticalPaintName + " " + reagent2.PracticalPaintName + " " +
 
                            reaction.Red + " " + reaction.Green + " " + reaction.Blue);
 
                        }
 
                    }
 
                }
 
            }
 
            using (StreamWriter writer = new(_clipFile, false))
 
            {
 
                foreach (var item1 in Clippers)
 
                {
 
                    foreach (var item2 in item1.Value)
 
                    {
 
                        if (item2.Value == ClipType.None) continue;
 
                        writer.WriteLine(item1.Key + " " + item2.Key + " " + (int)item2.Value);
 
                    }
 
                }
 
            }
 
        }
 

	
 
        public ClipType PairClipStatus(Reagent reagent1, Reagent reagent2)
 
        {
 
            if (Clippers.TryGetValue(reagent1.PracticalPaintName, out var item1))
 
            {
 
                if (item1.TryGetValue(reagent2.PracticalPaintName, out var clipType))
 
                {
 
                    return clipType;
 
                }
 
            }
 
            return ClipType.None;
 
        }
 

	
 
        public void SetPairClipStatus(Reagent reagent1, Reagent reagent2, ClipType clip)
 
        {
 
            if (Clippers.TryGetValue(reagent1.PracticalPaintName, out var item1))
 
            {
 
                if (item1.TryGetValue(reagent2.PracticalPaintName, out var clipType))
 
                {
 
                    if (clipType == clip) return;
 
                }
 
            }
 
            else
 
            {
 
                item1 = new Dictionary<string, ClipType>();
 
                Clippers.Add(reagent1.PracticalPaintName, item1);
 
            }
 

	
 
            item1[reagent2.PracticalPaintName] = clip;
 
            Save();
 
        }
 

	
 
        private void LoadRecipes(Dictionary<string, PaintRecipe> recipeDict, string filename, uint concentration)
 
        {
 
            foreach (PaintRecipe recipe in recipeDict.Values)
 
            {
 
                recipe.Clear();
 
            }
 
            string      recipeFile         = Path.Combine(Directory, filename);
 
            bool        inRecipe           = false;
 
            PaintRecipe testRecipe         = new();
 
            string?     currentRecipeColor = null;
 
            
 
            if (!File.Exists(recipeFile)) return;
 
            
 
            using StreamReader reader = new(recipeFile);
 
            
 
            string? line;
 
            while ((line = reader.ReadLine()) != null)
 
            {
 
                Match match = _recipeHeaderRegex.Match(line);
 
                if (match.Success)
 
                {
 
                    // Store previous recipe.
 
                    if ((currentRecipeColor != null) && testRecipe.IsValidForConcentration(concentration))
 
                    {
 
                        SetRecipe(currentRecipeColor, testRecipe);
 
                    }
 
                    testRecipe.Clear();
 
                    currentRecipeColor = match.Groups["colorname"].Value;
 
                    inRecipe  = true;
 
                }
 
                else if (inRecipe)
 
                {
 
                    match = _recipeIngredientRegex.Match(line);
 
                    
 
                    if (!match.Success) continue;
 
                    
 
                    string ingredient = match.Groups["ingredient"].Value;
 
                    uint   quantity   = uint.Parse(match.Groups["quantity"].Value);
 
                    
 
                    testRecipe.AddReagent(ingredient, quantity);
 
                }
 
            }
 

	
 
            if (!inRecipe || (currentRecipeColor == null)) return;
 
            
 
            // Store final recipe.
 
            if (testRecipe.IsValidForConcentration(concentration))
 
            {
 
                SetRecipe(currentRecipeColor, testRecipe);
 
            }
 
        }
 

	
 
        private void SaveRecipes(Dictionary<string, PaintRecipe> recipeDict, string filename)
 
        {
 
            string recipeFile = Path.Combine(Directory, filename);
 
            
 
            using StreamWriter writer = new(recipeFile, false);
 
            
 
            foreach (KeyValuePair<string, PaintRecipe> pair in recipeDict)
 
            {
 
                writer.WriteLine("--- Recipe: {0}", pair.Key);
 
                foreach (PaintRecipe.ReagentQuantity ingredient in pair.Value.Reagents)
 
                {
 
                    writer.WriteLine("{0,-14} | {1}", ingredient.Name, ingredient.Quantity);
 
                }
 
            }
 
        }
 

	
 
        private void DeleteRecipes(Dictionary<string, PaintRecipe> recipeDict, string filename)
 
        {
 
            string recipeFile = Path.Combine(Directory, filename);
 
            
 
            File.Delete(recipeFile);
 
            recipeDict.Clear();
 
        }
 

	
 
        public void LoadRecipes()
 
        {
 
            LoadRecipes(Recipes, PaintRecipeFile, PaintRecipe.PaintRecipeMinConcentration);
 
            LoadRecipes(RibbonRecipes, RibbonRecipeFile, PaintRecipe.RibbonRecipeMinConcentration);
 
        }
 

	
 
        public void SaveRecipes()
 
        {
 
            SaveRecipes(Recipes, PaintRecipeFile);
 
            SaveRecipes(RibbonRecipes, RibbonRecipeFile);
 
        }
 

	
 
        public void ClearRecipes()
 
        {
 
            DeleteRecipes(Recipes, PaintRecipeFile);
 
            DeleteRecipes(RibbonRecipes, RibbonRecipeFile);
 
        }
 

	
 
        public void ClearPaintRecipes()
 
        {
 
            DeleteRecipes(Recipes, PaintRecipeFile);
 
        }
 

	
 
        public void ClearRibbonRecipes()
 
        {
 
            DeleteRecipes(RibbonRecipes, RibbonRecipeFile);
 
        }
 

	
 
        public void ExportWikiRecipes(string file)
 
        {
 
            StreamWriter writer = new(file);
 
            ExportWikiFormat(writer, Recipes);
 
        }
 

	
 
        public void ExportWikiRibbons(string file)
 
        {
 
            StreamWriter writer = new StreamWriter(file);
 
            ExportWikiFormat(writer, this.RibbonRecipes);
 
        }
 

	
 
        public void ExportWikiRecipes(TextWriter writer)
 
        {
 
            ExportWikiFormat(writer, this.Recipes);
 
        }
 

	
 
        public void ExportWikiRibbons(TextWriter writer)
 
        {
 
            ExportWikiFormat(writer, this.RibbonRecipes);
 
        }
 
        
 
        public static void ExportWikiFormat(TextWriter writer, Dictionary<string, PaintRecipe> recipeDict)
 
        {
 
            using (writer)
 
            {
 
                writer.WriteLine("{| class='wikitable sortable' border=\"1\" style=\"background-color:#DEB887;\"");
 
                writer.WriteLine("! Color !! Recipe !! Missing Reactions? || Verified");
 
                foreach (PaintColor color in PaletteService.Colors)
 
                {
 
                    writer.WriteLine("|-");
 
                    string colorLine = "| ";
 
                    colorLine += "style=\"font-weight: bold; background-color: #" + color.Red.ToString("X2") + color.Green.ToString("X2") + color.Blue.ToString("X2") + ";";
 
                    
 
                    if (color.UseWhiteText)
 
                    {
 
                        // dark color gets light text
 
                        colorLine += " color: #FFFFFF;";
 
                    }
 
                    else
 
                    {
 
                        colorLine += "color: #000000;";
 
                    }
 
                    colorLine += "\" | " + color.Name + " || ";
 
                    if (recipeDict.TryGetValue(color.Name, out PaintRecipe? recipe))
 
                    {
 
                        foreach (PaintRecipe.ReagentQuantity ingredient in recipe.Reagents)
 
                        {
 
                            colorLine += " " + ingredient;
 
                        }
 
                    }
 
                    else
 
                    {
 
                        // no recipe
 
                    }
 
                    colorLine += " || ";
 

	
 
                    if (recipe == null)
 
                    {
 
                        colorLine += "?";
 
                    }
 
                    else if (recipe.HasMissingReactions())
 
                    {
 
                        colorLine += "Y";
 
                    }
 
                    else
 
                    {
 
                        colorLine += "N";
 
                    }
 

	
 
                    colorLine += " || N";
 
                    writer.WriteLine(colorLine);
 
                }
 
                writer.WriteLine("|}");
 
            }
 
        }
 

	
 
        public void ExportPaintMixRecipes(string file)
 
        {
 
            StreamWriter writer = new(file);
 
            ExportPaintMixFormat(writer, Recipes);
 
        }
 

	
 
        public void ExportPaintMixRibbons(string file)
 
        {
 
            StreamWriter writer = new StreamWriter(file);
 
            ExportPaintMixFormat(writer, this.RibbonRecipes);
 
        }
 

	
 
        public void ExportPaintMixRecipes(TextWriter writer)
 
        {
 
            ExportPaintMixFormat(writer, this.Recipes);
 
        }
 

	
 
        public void ExportPaintMixRibbons(TextWriter writer)
 
        {
 
            ExportWikiFormat(writer, this.RibbonRecipes);
 
        }
 

	
 
        public static void ExportPaintMixFormat(TextWriter writer, Dictionary<string, PaintRecipe> recipeDict)
 
        {
 
            using (writer)
 
            {
 
                foreach (PaintColor color in PaletteService.Colors)
 
                {
 
                    if (recipeDict.TryGetValue(color.Name, out PaintRecipe? recipe))
 
                    {
 
                        string colorLine = $"{color.Name} : ";
 
                        foreach (PaintRecipe.ReagentQuantity ingredient in recipe.Reagents)
 
                        {
 
                            colorLine += $" {ingredient.Name} {ingredient.Quantity}";
 
                        }
 
                        colorLine += $"  - #{color.Red:X2}{color.Green:X2}{color.Blue:X2}";
 
                        writer.WriteLine(colorLine);
 
                    }
 
                    else
 
                    {
 
                        // no recipe
 
                    }
 
                }
 
            }
 
        }
 

	
 
        public Reaction? FindReaction(Reagent? reagent1, Reagent? reagent2)
 
        {
 
            if ((reagent1 == null) || (reagent2 == null)) return null;
 
            return Reactions.Find(reagent1, reagent2);
 
        }
 

	
 
        public void SetReaction(Reagent reagent1, Reagent reagent2, Reaction reaction)
 
        {
 
            Reactions.Set(reagent1, reagent2, reaction);
 
        }
 

	
 
        public void ClearReaction(Reagent reagent1, Reagent reagent2)
 
        {
 
            Reactions.Remove(reagent1, reagent2);
 
        }
 

	
 
        public void SetRecipe(PaintRecipe recipe)
 
        {
 
            SetRecipe(PaletteService.FindNearest(recipe.ReactedColor), recipe);
 
        }
 

	
 
        public void SetRecipe(string colorName, PaintRecipe recipe)
 
        {
 
            if (Recipes.TryGetValue(colorName, out PaintRecipe? profileRecipe))
 
            {
 
                profileRecipe.CopyFrom(recipe);
 
            }
 
            else
 
            {
 
                Recipes.Add(colorName, new PaintRecipe(recipe));
 
            }
 
        }
 

	
 
        public void SetRibbonRecipe(PaintRecipe recipe)
 
        {
 
            SetRibbonRecipe(PaletteService.FindNearest(recipe.ReactedColor), recipe);
 
        }
 

	
 
        public void SetRibbonRecipe(string colorName, PaintRecipe recipe)
 
        {
 
            if (RibbonRecipes.TryGetValue(colorName, out PaintRecipe? profileRecipe))
 
            {
 
                profileRecipe.CopyFrom(recipe);
 
            }
 
            else
 
            {
 
                RibbonRecipes.Add(colorName, new PaintRecipe(recipe));
 
            }
 
        }
 
    }
 
}
ViewModels/MainWindowViewModel.cs
Show inline comments
 
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);
 
        }
 

	
 
        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<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 async void ImportProfile()
 
        {
 
            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 async void ExportForPP(object f)
 
        {
 
            string? fileName = await GetSaveFileName("Save Practical Paint File", TxtFileFilters, "reactions.txt");
 
            
 
            if (string.IsNullOrEmpty(fileName)) return;
 
            
 
            try
 
            {
 
                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 async void ImportFromPP()
 
        {
 
            string? fileName = await GetLoadFileName("Import Reactions File", TxtFileFilters, "reactions.txt");
 
            
 
            if (string.IsNullOrEmpty(fileName)) return;
 

	
 
            try
 
            {
 
                ProfileManager.CurrentProfile?.ImportFromPP(fileName);
 
            }
 
            catch (Exception e)
 
            {
 
                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", NoFileFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            {
 
                ProfileManager.CurrentProfile?.ExportWikiRecipes(fileName);
 
            }
 
        }
 
        
 
        public static async void ExportRibbonRecipes()
 
        {
 
            string? fileName = await GetSaveFileName("Export Ribbon Recipes", NoFileFilters);
 
            if (!string.IsNullOrEmpty(fileName))
 
            {
 
                ProfileManager.CurrentProfile?.ExportWikiRibbons(fileName);
 
            }
 
        }
 

	
 
        public static async void ExportPaintMixRecipes()
 
        {
 
            string? fileName = await GetSaveFileName("Export paint_mix recipes", NoFileFilters, "paint_recipes.txt");
 
            if (!string.IsNullOrEmpty(fileName))
 
            {
 
                ProfileManager.CurrentProfile?.ExportPaintMixRecipes(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();
 
        }
 
        
 
        public static async void CopyRibbonRecipes()
 
        {
 
            StringWriter writer = new();
 
            ProfileManager.CurrentProfile?.ExportWikiRibbons(writer);
 
            IClipboard clipboard = Application.Current.Clipboard;
 
            await writer.FlushAsync();
 
            await clipboard.SetTextAsync(writer.ToString());
 
            writer.Close();
 
        }
 

	
 
        public async Task ShowScreenSettings()
 
        {
 
            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, 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         = 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, 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         = 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
 
<Window xmlns="https://github.com/avaloniaui"
 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
        xmlns:vm="using:DesertPaintCodex.ViewModels"
 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 
        xmlns:views="clr-namespace:DesertPaintCodex.Views"
 
        mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="800"
 
        Width="640" Height="800" MinWidth="600" MinHeight="500" Topmost="True"
 
        x:Class="DesertPaintCodex.Views.MainWindow"
 
        Icon="/Assets/desert_paint_codex_icon.ico"
 
        Title="Desert Paint Codex">
 

	
 
    <Design.DataContext>
 
        <vm:MainWindowViewModel/>
 
    </Design.DataContext>
 
    
 
    <Window.DataContext>
 
        <vm:MainWindowViewModel/>
 
    </Window.DataContext>
 
    
 
    <Window.Styles>
 
        <!--
 
        <Style Selector="Menu">
 
            <Setter Property="Background" Value="#282828"/>
 
        </Style>
 
        -->
 
        <Style Selector="ContentControl">
 
            <Setter Property="Margin" Value="0 5 0 0"/>
 
        </Style>
 
        <Style Selector="TextBlock.StatusBar">
 
            <Setter Property="Margin" Value="5"/>
 
        </Style>
 
        <Style Selector="TabControl.ActivityPicker WrapPanel">
 
            <Setter Property="Background" Value="{DynamicResource GutterBackgroundBrush}"/>
 
        </Style>
 
    
 
        <Style Selector="TabControl.ActivityPicker">
 
            <Setter Property="Background" Value="{DynamicResource FlatBackgroundBrush}"/>
 
        </Style>
 
    
 
        <Style Selector="TabControl.ActivityPicker > TabItem">
 
            <Setter Property="Padding" Value="15 5"/>
 
        </Style>
 
    
 
        <Style Selector="TabControl.ActivityPicker > TabItem:pointerover">
 
            <Setter Property="Foreground" Value="#000000"/>
 
        </Style>
 

	
 
        <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>
 
                        <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}">
 
                        <MenuItem Header="Export Paint Recipes (Wiki format)..." Command="{Binding ExportPaintRecipes}">
 
                            <ToolTip.Tip>
 
                                Exports recipes in Wiki table format.
 
                            </ToolTip.Tip>
 
                        </MenuItem>
 
                        <MenuItem Header="Export Ribbon Recipes..." Command="{Binding ExportRibbonRecipes}">
 
                        <MenuItem Header="Export Ribbon Recipes (Wiki format)..." 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 Header="Export Paint Recipes (paint_mix format)..." Command="{Binding ExportPaintMixRecipes}">
 
                        <ToolTip.Tip>
 
                          Exports recipes in Wiki table format.
 
                        </ToolTip.Tip>
 
                      </MenuItem>
 
                    </MenuItem>
 

	
 
                    <Separator/>
 

	
 
                    <MenuItem Header="Screen Settings..." Command="{Binding ShowScreenSettings}"></MenuItem>
 
                    <Separator/>
 

	
 
                    <MenuItem Header="Exit" Command="{Binding Exit}"></MenuItem>
 
                </MenuItem>
 

	
 
                <MenuItem Header="_Help">
 
                     <MenuItem Header="Documentation" Command="{Binding OpenBrowser}" CommandParameter="https://repos.malkyne.org/ATITD-Tools/Desert-Paint-Codex"></MenuItem>
 
                     <MenuItem Header="About..." Command="{Binding ShowAbout}"></MenuItem>
 
                </MenuItem>
 
            </Menu>
 
            
 
            <Border DockPanel.Dock="Top" BorderThickness="2" Background="{DynamicResource GutterBackgroundBrush}"></Border>
 

	
 

	
 
            <TextBlock DockPanel.Dock="Bottom" Classes="StatusBar"
 
                       Text="{Binding StatusText}"
 
                       HorizontalAlignment="Left" VerticalAlignment="Center" Height="18"/>
 
            
 
            <Border DockPanel.Dock="Bottom" BorderThickness="2" Background="{DynamicResource GutterBackgroundBrush}"></Border>
 
            
 
            <TabControl Classes="ActivityPicker">
 
                <TabItem Header="EXPERIMENT LOG" VerticalContentAlignment="Center">
 
                    <views:ExperimentLogView />
 
                </TabItem>
 
                <TabItem Header="SIMULATOR" VerticalContentAlignment="Center">
 
                    <views:SimulatorView />
 
                </TabItem>
 
                <TabItem Header="RECIPE GENERATOR" VerticalContentAlignment="Center">
 
                    <views:RecipeGeneratorView />
 
                </TabItem>
 
            </TabControl>
 
        </DockPanel>
 
    </Grid>
 
    
 

	
 
</Window>
0 comments (0 inline, 0 general)