Changeset - 63ed2e1d582c
[Not reviewed]
default
3 7 1
Jason Maltzen (jmaltzen) - 9 years ago 2015-12-27 09:37:12
jason.maltzen@unsanctioned.net
Switch recipe generation to breadth-first searching. Move ingredients editing to recipe generator. Add functions to copy single recipe and all recipes to clipboard.
11 files changed with 1379 insertions and 852 deletions:
0 comments (0 inline, 0 general)
DesertPaintLab.csproj
Show inline comments
...
 
@@ -63,48 +63,49 @@
 
    <Compile Include="ReagentManager.cs" />
 
    <Compile Include="Reagent.cs" />
 
    <Compile Include="gtk-gui\MainWindow.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.FirstRunDialog.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.SelectProfileDialog.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.NewProfileDialog.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.PaintSwatch.cs" />
 
    <Compile Include="SimulatorWindow.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.SimulatorWindow.cs" />
 
    <Compile Include="ScreenCheckDialog.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.ScreenCheckDialog.cs" />
 
    <Compile Include="FileUtils.cs" />
 
    <Compile Include="PaintRecipe.cs" />
 
    <Compile Include="ReactionRecorder.cs" />
 
    <Compile Include="ReactionStatusWindow.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.ReactionStatusWindow.cs" />
 
    <Compile Include="RecipeGeneratorWindow.cs">
 
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 
    </Compile>
 
    <Compile Include="gtk-gui\DesertPaintLab.RecipeGeneratorWindow.cs" />
 
    <Compile Include="ReactionSet.cs" />
 
    <Compile Include="RecipeGenerator.cs" />
 
    <Compile Include="ReagentWindow.cs" />
 
    <Compile Include="gtk-gui\DesertPaintLab.ReagentWindow.cs" />
 
    <Compile Include="RecipeSearchNode.cs" />
 
  </ItemGroup>
 
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 
  <ProjectExtensions>
 
    <MonoDevelop>
 
      <Properties>
 
        <Policies>
 
          <TextStylePolicy inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/x-csharp" />
 
          <CSharpFormattingPolicy IndentSwitchBody="True" IndentBlocksInsideExpressions="True" AnonymousMethodBraceStyle="NextLine" PropertyBraceStyle="NextLine" PropertyGetBraceStyle="NextLine" PropertySetBraceStyle="NextLine" EventBraceStyle="NextLine" EventAddBraceStyle="NextLine" EventRemoveBraceStyle="NextLine" StatementBraceStyle="NextLine" ElseNewLinePlacement="NewLine" CatchNewLinePlacement="NewLine" FinallyNewLinePlacement="NewLine" WhileNewLinePlacement="DoNotCare" ArrayInitializerWrapping="DoNotChange" ArrayInitializerBraceStyle="NextLine" BeforeMethodDeclarationParentheses="False" BeforeMethodCallParentheses="False" BeforeConstructorDeclarationParentheses="False" NewLineBeforeConstructorInitializerColon="NewLine" NewLineAfterConstructorInitializerColon="SameLine" BeforeDelegateDeclarationParentheses="False" NewParentheses="False" SpacesBeforeBrackets="False" inheritsSet="Mono" inheritsScope="text/x-csharp" scope="text/x-csharp" />
 
        </Policies>
 
        <GtkDesignInfo generateGettext="False" />
 
      </Properties>
 
    </MonoDevelop>
 
  </ProjectExtensions>
 
  <ItemGroup>
 
    <None Include="data\colors.txt">
 
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 
    </None>
 
    <None Include="data\ingredients.txt">
 
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 
    </None>
 
    <None Include="data\template\dp_reactions.txt">
 
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 
    </None>
 
    <None Include="data\template\ingredients.txt">
PaintRecipe.cs
Show inline comments
...
 
@@ -80,48 +80,59 @@ namespace DesertPaintLab
 
            foreach (RecipeIngredient copyIngredient in other.recipe)
 
            {
 
                RecipeIngredient ingredient = new RecipeIngredient(copyIngredient);
 
                this.recipe.Add(ingredient);
 
            }
 
        }
 

	
 
        public void CopyFrom(PaintRecipe other)
 
        {
 
            this.dirty = true;
 
            this.reactions = other.reactions;
 
            this.reagents.Clear();
 
            foreach (string reagentName in other.reagents)
 
            {
 
                this.reagents.Add(reagentName);
 
            }
 
            this.recipe.Clear();
 
            foreach (RecipeIngredient otherIngredient in other.recipe)
 
            {
 
                RecipeIngredient ingredient = new RecipeIngredient(otherIngredient);
 
                this.recipe.Add(ingredient);
 
            }
 
        }
 

	
 
        override public string ToString()
 
        {
 
            string result = Palette.FindNearest(ReactedColor);
 
            result += " |";
 
            foreach (RecipeIngredient ingr in recipe)
 
            {
 
                result += " " + ingr.ToString();
 
            }
 
            return result;
 
        }
 

	
 
        public List<RecipeIngredient> Ingredients
 
        {
 
            get {
 
                return recipe;
 
            }
 
        }
 

	
 
        public ReactionSet Reactions
 
        {
 
            get
 
            {
 
                return reactions;
 
            }
 
            set
 
            {
 
                dirty = true;
 
                reactions = value;
 
            }
 
        }
 

	
 
        public PaintColor ReactedColor
 
        {
 
            get
 
            {
PlayerProfile.cs
Show inline comments
...
 
@@ -385,76 +385,82 @@ namespace DesertPaintLab
 
            }
 
        }
 

 
        public void SaveRecipes()
 
        {
 
            if (recipes != null)
 
            {
 
                string recipeFile = System.IO.Path.Combine(directory, "dp_recipes.txt");
 
                using (StreamWriter writer = new StreamWriter(recipeFile, false))
 
                {
 
                    foreach (KeyValuePair<string, PaintRecipe> pair in recipes)
 
                    {
 
                        writer.WriteLine("--- Recipe: {0}", pair.Key);
 
                        foreach (PaintRecipe.RecipeIngredient ingredient in pair.Value.Ingredients)
 
                        {
 
                            writer.WriteLine("{0,-14} | {1}", ingredient.name, ingredient.quantity);
 
                        }
 
                    }
 
                }
 
            }
 
        }
 

 
        public void ExportWikiRecipes(string file)
 
        {
 
            StreamWriter writer = new StreamWriter(file);
 
            ExportWikiFormat(writer);
 
        }
 
		
 
        public void ExportWikiFormat(TextWriter writer)
 
        {
 
            PaintRecipe recipe;
 
            using (StreamWriter writer = new StreamWriter(file))
 
            using (writer)
 
            {
 
                writer.WriteLine("{| class='wikitable sortable' border=\"1\" style=\"background-color:#DEB887;\"");
 
                writer.WriteLine("! Color !! Recipe !! Verified");    
 
                foreach (PaintColor color in Palette.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.Red < 128 && color.Green < 128 && color.Blue < 128)
 
                    {
 
                        // dark color gets light text
 
                        colorLine += " color: #FFFFFF;";
 
                    }
 
                    colorLine += "\" | " + color.Name + " || ";
 
                    if (recipes.TryGetValue(color.Name, out recipe))
 
                    {
 
                        foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients)
 
                        {
 
                            colorLine += " " + ingredient.ToString();
 
                        }
 
                    } 
 
                    else
 
                    {
 
                        // no recipe
 
                    }
 
                    colorLine += " || ";
 
                    writer.WriteLine(colorLine);
 
                }
 
                writer.WriteLine("|}");
 
            }
 
        }
 
		
 
        
 
		public Reaction FindReaction(Reagent reagent1, Reagent reagent2)
 
		{
 
            return reactions.Find(reagent1, reagent2);
 
		}
 
		
 
		public void SetReaction(Reagent reagent1, Reagent reagent2, Reaction reaction)
 
		{
 
            reactions.Set(reagent1, reagent2, reaction);
 
		}
 

 
        public void SetRecipe(PaintRecipe recipe)
 
        {
 
            string colorName = Palette.FindNearest(recipe.ReactedColor);
 
            recipes[colorName].CopyFrom(recipe);
 
        }
 
	}
 
}
 

RecipeGenerator.cs
Show inline comments
...
 
@@ -36,788 +36,469 @@ namespace DesertPaintLab
 

	
 
        public NewRecipeEventArgs(string color, PaintRecipe recipe)
 
        {
 
            this.color = color;
 
            this.recipe = recipe;
 
        }
 

	
 
        public string Color
 
        {
 
            get {
 
                return color;
 
            }
 
        }
 

	
 
        public PaintRecipe Recipe
 
        {
 
            get {
 
                return recipe;
 
            }
 
        }
 
    }
 

	
 
    public class RecipeGenerator
 
    {
 
        protected class SearchNode
 
        {
 
            //int initialReagentCount;
 
            uint[] reagents;
 
            public uint[] Reagents
 
            {
 
                get
 
                {
 
                    return reagents;
 
                }
 
            }
 
            uint INVALID_REAGENT;
 
            int nextReagentPos;
 
            public int ReagentCount
 
            {
 
                get
 
                {
 
                    return nextReagentPos;
 
                }
 
            }
 

	
 
            bool[] reagentInUse;
 
            List<Reagent> costSortedReagents;
 
            PaintRecipe testRecipe = null;
 
            public PaintRecipe TestRecipe
 
            {
 
                get
 
                {
 
                    return testRecipe;
 
                }
 
                set
 
                {
 
                    testRecipe = value;
 
                }
 
            }
 

	
 
            public int InitialCount { get; private set; }
 
            public uint CurrentTargetQuantity { get; set; }
 
            public uint MaxQuantity { get; set; }
 
            public uint UsedQuantity { get; private set; }
 
            public uint CatalystCount { get; set; }
 

	
 
            uint maxReagents;
 
            public uint MaxReagents
 
            {
 
                get
 
                {
 
                    return maxReagents;
 
                }
 
                set
 
                {
 
                    maxReagents = value;
 
                    currentWeights = new uint[maxReagents];
 
                }
 
            }
 

	
 
            uint[] currentWeights;
 
            public uint[] CurrentWeights
 
            {
 
                get
 
                {
 
                    return currentWeights;
 
                }
 
            }
 

	
 
            public SearchNode(List<Reagent> costSortedReagents, uint[] reagents)
 
            {
 
                this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
                this.reagents = new uint[costSortedReagents.Count];
 
                INVALID_REAGENT = (uint)costSortedReagents.Count;
 
                nextReagentPos = reagents.Length;
 
                for (int i = this.reagents.Length-1; i >= this.reagents.Length; --i)
 
                {
 
                    reagents[i] = INVALID_REAGENT;
 
                }
 
                for (int i = reagents.Length-1; i >= 0; --i)
 
                {
 
                    this.reagents[i] = reagents[i];
 
                    if (reagents[i] == INVALID_REAGENT)
 
                    {
 
                        nextReagentPos = i;
 
                    }
 
                }
 
                reagentInUse = new bool[costSortedReagents.Count];
 
                for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
                {
 
                    reagentInUse[reagentIdx] = false;
 
                }
 
                foreach (uint reagentIdx in this.reagents)
 
                {
 
                    if (reagentIdx != INVALID_REAGENT)
 
                    {
 
                        reagentInUse[reagentIdx] = true;
 
                    }
 
                }
 
                InitialCount = nextReagentPos;
 
                MaxReagents = (uint)nextReagentPos; // better set this later!
 
                UsedQuantity = 0;
 
            }
 

	
 
            // top-level search
 
            public SearchNode(List<Reagent> costSortedReagents, uint startReagent)
 
            {
 
                this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
                this.reagents = new uint[costSortedReagents.Count];
 
                INVALID_REAGENT = (uint)costSortedReagents.Count;
 
                nextReagentPos = 0;
 
                for (int i = 0; i < reagents.Length; ++i)
 
                {
 
                    this.reagents[i] = INVALID_REAGENT;
 
                }
 
                reagentInUse = new bool[costSortedReagents.Count];
 
                for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
                {
 
                    reagentInUse[reagentIdx] = false;
 
                }
 
                this.reagents[nextReagentPos++] = NextFreeReagent(startReagent);
 
                InitialCount = 1; // don't iterate up beyond the start reagent
 
                MaxReagents = 1;
 
                UsedQuantity = 0;
 
            }
 

	
 
            public SearchNode(List<Reagent> costSortedReagents)
 
            {
 
                this.costSortedReagents = costSortedReagents;
 
                this.reagents = new uint[costSortedReagents.Count];
 
                INVALID_REAGENT = (uint)costSortedReagents.Count;
 
                nextReagentPos = 0;
 
                for (int i = 0; i < reagents.Length; ++i)
 
                {
 
                    this.reagents[i] = INVALID_REAGENT;
 
                }
 
                reagentInUse = new bool[costSortedReagents.Count];
 
                for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
                {
 
                    reagentInUse[reagentIdx] = false;
 
                }
 
                this.reagents[nextReagentPos++] = NextFreeReagent(0);
 
                InitialCount = 0;
 
                MaxReagents = 1;
 
                UsedQuantity = 0;
 
            }
 

	
 
            public Reagent Reagent(int idx)
 
            {
 
                return costSortedReagents[(int)reagents[idx]];
 
            }
 

	
 
            public uint LastReagent
 
            {
 
                get
 
                {
 
                    return reagents[nextReagentPos - 1];
 
                }
 
            }
 

	
 
            public void RemoveLastReagent()
 
            {
 
                uint reagentIdx = reagents[nextReagentPos-1];
 
                ReleaseReagent(reagentIdx);
 
                if (costSortedReagents[(int)reagentIdx].IsCatalyst)
 
                {
 
                    --CatalystCount;
 
                }
 
                reagents[nextReagentPos-1] = INVALID_REAGENT;
 
                --nextReagentPos;
 
            }
 

	
 
            public void ReplaceLastReagent(uint reagentIdx)
 
            {
 
                uint oldReagentIdx = reagents[nextReagentPos-1];
 
                ReleaseReagent(oldReagentIdx);
 
                reagents[nextReagentPos-1] = reagentIdx;
 
                if (costSortedReagents[(int)oldReagentIdx].IsCatalyst)
 
                {
 
                    --CatalystCount;
 
                }
 
                if (costSortedReagents[(int)reagentIdx].IsCatalyst)
 
                {
 
                    ++CatalystCount;
 
                }
 
            }
 
        public enum SearchType {
 
            DEPTH_FIRST,
 
            BREADTH_FIRST
 
        };
 

	
 
            public uint NextFreeReagent(uint startIdx)
 
            {
 
                uint idx = startIdx;
 
                for (; idx < costSortedReagents.Count; ++idx)
 
                {
 
                    bool inUse = reagentInUse[idx];
 
                    if ((inUse == false) && (costSortedReagents[(int)idx].Enabled))
 
                    {
 
                        //Console.WriteLine("Found free reagent idx {0}", idx);
 
                        reagentInUse[idx] = true;
 
                        return idx;
 
                    }
 
                }
 
                //Console.WriteLine("Failed to find free reagent.");
 
                return (uint)costSortedReagents.Count;
 
            }
 
    
 
            private void ReleaseReagent(uint reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
    
 
            public bool AddNextReagent()
 
            {
 
                bool ok = (nextReagentPos < MaxReagents);
 
                if (ok)
 
                {
 
                    uint nextReagent = NextFreeReagent(0);
 
                    reagents[nextReagentPos++] = nextReagent;
 
                    if (costSortedReagents[(int)nextReagent].IsCatalyst)
 
                    {
 
                        ++CatalystCount;
 
                    }
 
                    InitForQuantity(CurrentTargetQuantity);
 
                }
 
                return ok;
 
            }
 

	
 
            public void InitForQuantity(uint quantity)
 
            {
 
                //System.Console.WriteLine("Target quantity: {0}, reagent count: {1}", quantity, reagents.Count);
 
                CurrentTargetQuantity = quantity;
 
                if (CurrentTargetQuantity < (10 + CatalystCount))
 
                {
 
                    return;
 
                }
 
                UsedQuantity = 0;
 
                uint remainingReagents = ((uint)nextReagentPos - CatalystCount);
 
                uint remainingWeight = CurrentTargetQuantity - CatalystCount;
 
                for (int i = 0; i < nextReagentPos; ++i)
 
                {
 
                    Reagent reagent = Reagent(i);
 
    
 
                    if (reagent.IsCatalyst)
 
                    {
 
                        currentWeights[i] = 1;
 
                        ++UsedQuantity;
 
                    }
 
                    else
 
                    {
 
                        uint weight = (uint)Math.Min(remainingWeight - (remainingReagents-1), reagent.RecipeMax);
 
                        remainingWeight -= weight;
 
                        currentWeights[i] = weight;
 
                        UsedQuantity += weight;
 
                    }
 
                    --remainingReagents;
 
                }
 
            }
 

	
 
            public void SetWeight(int idx, uint quantity)
 
            {
 
                UsedQuantity -= currentWeights[idx];
 
                currentWeights[idx] = quantity;
 
                UsedQuantity += quantity;
 
            }
 

	
 
            public void SaveState(StreamWriter writer)
 
            {
 
                writer.WriteLine("---SearchNode---");
 
                writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
                writer.WriteLine("Reagents: {0}", nextReagentPos);
 
                for (int i = 0; i < nextReagentPos; ++i)
 
                {
 
                    uint idx = reagents[i];
 
                    uint weight = currentWeights[i];
 
                    writer.WriteLine("Reagent: {0},{1},{2}", idx, reagentInUse[idx] ? 1 : 0, weight);
 
                }
 
                // pulled from parent: List<Reagent> costSortedReagents;
 
                // new on construct: PaintRecipe testRecipe = null;
 
                writer.WriteLine("CurrentTargetQuantity: {0}", CurrentTargetQuantity);
 
                writer.WriteLine("MaxQuantity: {0}", MaxQuantity);
 
                writer.WriteLine("UsedQuantity: {0}", UsedQuantity);
 
                writer.WriteLine("CatalystCount: {0}", CatalystCount);
 
                writer.WriteLine("InitialCount: {0}", InitialCount);
 
                writer.WriteLine("---EndNode---");
 
                
 
            }
 

	
 
            static Regex keyValueRegex = new Regex(@"(\w+)\:\s*(.*)\s*$");
 
            static Regex reagentPartsRegex = new Regex(@"(?<id>\d+),(?<inUse>\d+),(?<weight>\d+)");
 
            public bool LoadState(StreamReader reader)
 
            {
 
                string line = reader.ReadLine();
 
                if (!line.Equals("---SearchNode---"))
 
                {
 
                    return false;
 
                }
 
        public SearchType Mode { get; set; }
 

	
 
                bool success = true;
 
                Match match;
 
                while ((line = reader.ReadLine()) != null)
 
                {
 
                    if (line.Equals("---EndNode---"))
 
                    {
 
                        break;
 
                    }
 
                    match = keyValueRegex.Match(line);
 
                    if (match.Success)
 
                    {
 
                        switch (match.Groups[1].Value)
 
                        {
 
                            case "Reagents":
 
                                {
 
                                    //int reagentCount = int.Parse(match.Groups[2].Value);
 
                                    for (int i = 0; i < reagents.Length; ++i)
 
                                    {
 
                                        reagents[i] = INVALID_REAGENT;
 
                                        reagentInUse[i] = false;
 
                                    }
 
                                    nextReagentPos = 0;
 
                                }
 
                                break;
 
                            case "Reagent":
 
                                {
 
                                    Match reagentInfo = reagentPartsRegex.Match(match.Groups[2].Value);
 
                                    if (reagentInfo.Success)
 
                                    {
 
                                        uint reagentId = uint.Parse(reagentInfo.Groups["id"].Value);
 
                                        int isInUse = int.Parse(reagentInfo.Groups["inUse"].Value);
 
                                        uint weight = uint.Parse(reagentInfo.Groups["weight"].Value);
 
                                        reagents[nextReagentPos] = reagentId;
 
                                        currentWeights[nextReagentPos] = weight;
 
                                        if (isInUse != 0)
 
                                        {
 
                                            if (reagentId != INVALID_REAGENT)
 
                                            {
 
                                                reagentInUse[reagentId] = true;
 
                                            }
 
                                        }
 
                                        ++nextReagentPos;
 
                                    }
 
                                    else
 
                                    {
 
                                        success = false;
 
                                    }
 
                                }
 
                                break;
 
                            case "CurrentTargetQuantity":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    CurrentTargetQuantity = value;
 
                                }
 
                                break;
 
                            case "MaxQuantity":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    MaxQuantity = value;
 
                                }
 
                                break;
 
                            case "MaxReagents":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    MaxReagents = value;
 
                                }
 
                                break;
 
                            case "UsedQuantity":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    UsedQuantity = value;
 
                                }
 
                                break;
 
                            case "CatalystCount":
 
                                {
 
                                    uint value = uint.Parse(match.Groups[2].Value);
 
                                    CatalystCount = value;
 
                                }
 
                                break;
 
                            case "InitialCount":
 
                                {
 
                                    int value = int.Parse(match.Groups[2].Value);
 
                                    InitialCount = value;
 
                                }
 
                                break;
 
                            default:
 
                                success = false;
 
                                break;
 
                        }
 
                    }
 
                    else
 
                    {
 
                        success = false;
 
                        break;
 
                    }
 
                }
 
                return success;
 
            }
 
        }
 

	
 
        const uint DEFAULT_MAX_QUANTITY = 14; // minimum recipe: 10 base + 4 catalysts
 
        const uint DEFAULT_MAX_REAGENTS = 5;
 

	
 
        //uint maxQuantity; // maximum number of total ingredients
 
        uint maxReagents; // maximum number of reagents to use in the recipe
 
        uint fullQuantityDepth; // at or equal this number of reagents, ignore ingredient settings for max quantity
 
        uint fullQuantity;  // The max number of a reagent to use at full quantity
 
        public uint MaxQuantity { get; private set; } // maximum number of total ingredients
 
        public uint MaxReagents { get; private set; } // maximum number of reagents to use in the recipe
 
        public uint FullQuantityDepth { get; private set; } // at or equal this number of reagents, ignore ingredient settings for max quantity
 
        public uint FullQuantity { get; private set; }  // The max number of a reagent to use at full quantity
 

	
 
        ReactionSet reactions;
 
        bool running = false;
 

	
 
        int runningThreads = 0;
 
        
 
        SortedDictionary<string, uint> recipeCosts = new SortedDictionary<string, uint>();
 
        SortedDictionary<string, PaintRecipe> recipes = new SortedDictionary<string, PaintRecipe>();
 

	
 
        uint totalReagents;
 

	
 
        List<Reagent> costSortedReagents = new List<Reagent>();
 

	
 
        ConcurrentQueue<SearchNode> searchQueue = new ConcurrentQueue<SearchNode>();
 
        ConcurrentQueue<RecipeSearchNode> searchQueue = new ConcurrentQueue<RecipeSearchNode>();
 

	
 
        int recipeCount = 0;
 
        ulong recipeCount = 0;
 

	
 
        List<Thread> generatorThreads = new List<Thread>();
 
        Object workerLock = new Object();
 

	
 
        bool requestCancel = false;
 

	
 
        StreamWriter log;
 

	
 
        // events
 
        public event EventHandler Finished;
 
        public event EventHandler Progress;
 
        public event EventHandler<NewRecipeEventArgs> NewRecipe;
 

	
 
        public RecipeGenerator(ReactionSet reactions)
 
        {
 
            Mode = SearchType.BREADTH_FIRST;
 
            this.reactions = reactions;
 
            foreach (PaintColor color in Palette.Colors)
 
            {
 
                recipes.Add(color.Name, new PaintRecipe());
 
                recipeCosts.Add(color.Name, uint.MaxValue);
 
            }
 
        }
 

	
 
        public SortedDictionary<string, PaintRecipe> Recipes
 
        {
 
            get
 
            {
 
                return recipes;
 
            }
 
        }
 

	
 
        public int RecipeCount
 
        public ulong RecipeCount
 
        {
 
            get
 
            {
 
                return recipeCount;
 
            }
 
        }
 

	
 
        public bool CanResume
 
        {
 
            get
 
            {
 
                return (!running && (searchQueue.Count > 0));
 
            }
 
        }
 

	
 
        public string Log
 
        {
 
            set
 
            {
 
                if (value != null)
 
                {
 
                    log = new StreamWriter(value);
 
                }
 
                else
 
                {
 
                    log = null;
 
                }
 
            }
 
        }
 

	
 
        private class ReagentCostSort : IComparer<Reagent>
 
        {
 
            public int Compare(Reagent reagent1, Reagent reagent2)
 
            {
 
                return (int)reagent1.Cost - (int)reagent2.Cost;
 
            }
 
        }
 

	
 
        public void InitRecipes(SortedDictionary<string, PaintRecipe> initialRecipes)
 
        {
 
            if (running)
 
            {
 
                return;
 
            }
 
            foreach (PaintRecipe recipe in initialRecipes.Values)
 
            {
 
                //PaintRecipe recipeCopy = new PaintRecipe(recipe);
 
                AddCheapestRecipe(recipe);
 
            }
 
        }
 

	
 
        private void InitSortedReagents()
 
        {
 
            costSortedReagents.Clear();
 
            foreach (string name in ReagentManager.Names)
 
            {
 
                Reagent reagent = ReagentManager.GetReagent(name);
 
                costSortedReagents.Add(reagent);
 
            }
 
            costSortedReagents.Sort(new ReagentCostSort());
 
        }
 

	
 
        public void BeginRecipeGeneration(uint maxQuantity, uint maxReagents, uint fullQuantityDepth, uint fullQuantity)
 
        {
 
            if (running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 

	
 
            //this.maxQuantity = maxQuantity;
 
            this.maxReagents = maxReagents;
 
            this.fullQuantity = fullQuantity;
 
            this.fullQuantityDepth = fullQuantityDepth;
 
            this.MaxQuantity = maxQuantity;
 
            this.MaxReagents = maxReagents;
 
            this.FullQuantity = fullQuantity;
 
            this.FullQuantityDepth = fullQuantityDepth;
 

	
 
            // first, sort reagents by cost.
 
            InitSortedReagents();
 
            this.maxReagents = (uint)Math.Min(costSortedReagents.Count, this.maxReagents);
 

	
 
            totalReagents = (uint)costSortedReagents.Count;
 

	
 
            // Pre-populate recipes list with:
 
            // 1) 1-ingredient recipes @ 10db for all enabled ingredients with a count >= 10
 
            // 2) any previously-generated recipes
 
            int enabledReagentCount = 0;
 
            PaintRecipe recipe = new PaintRecipe();
 
            recipe.Reactions = reactions;
 
            foreach (Reagent reagent in costSortedReagents)
 
            {
 
                if (!reagent.IsCatalyst && reagent.RecipeMax >= 10 && reagent.Enabled)
 
                if (reagent.Enabled)
 
                {
 
                    recipe.Clear();
 
                    recipe.AddReagent(reagent.Name, 10);
 
                    AddCheapestRecipe(recipe);
 
                    if (!reagent.IsCatalyst && ((reagent.RecipeMax >= 10) || ((FullQuantityDepth > 0) && (FullQuantity >= 10))))
 
                    {
 
                        recipe.Clear();
 
                        recipe.AddReagent(reagent.Name, 10);
 
                        AddCheapestRecipe(recipe);
 
                    }
 
                    ++enabledReagentCount;
 
                }
 
            }
 
            this.MaxReagents = (uint)Math.Min(enabledReagentCount, this.MaxReagents);
 

	
 
            while (!searchQueue.IsEmpty)
 
            {
 
                SearchNode node;
 
                RecipeSearchNode node;
 
                searchQueue.TryDequeue(out node);
 
            }
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                if (costSortedReagents[(int)reagentIdx].Enabled)
 
                {
 
                    SearchNode initialNode = new SearchNode(costSortedReagents, reagentIdx);
 
                    RecipeSearchNode initialNode = new RecipeSearchNode(costSortedReagents, reagentIdx);
 
                    initialNode.FullQuantity = FullQuantity;
 
                    initialNode.FullQuantityDepth = FullQuantityDepth;
 
                    initialNode.MaxQuantity = maxQuantity;
 
                    initialNode.MaxReagents = maxReagents;
 
                    searchQueue.Enqueue(initialNode);
 
                }
 
            }
 

	
 
            recipeCount = 0;
 

	
 
            if (log != null)
 
            {
 
                log.WriteLine("Begin recipe generation: MaxQuantity={0} MaxReagents={1} FullQuantity={2} FullQuantityDepth={3}", MaxQuantity, MaxReagents, FullQuantity, FullQuantityDepth);
 
            }
 
            
 
            // start worker threads to do the actual work
 

	
 
            ResumeRecipeGeneration();
 
        }
 

	
 
        public void ResumeRecipeGeneration()
 
        {
 
            if (running)
 
            {
 
                // Already running - don't start again
 
                return;
 
            }
 
            running = true;
 
            requestCancel = false;
 

	
 
            //System.Console.WriteLine("Resuming recipe generation: pre-threads={0} reagent count={1} search queue={2}", runningThreads, costSortedReagents.Count, searchQueue.Count);
 
            if (log != null)
 
            {
 
                log.WriteLine("Resuming recipe generation: pre-threads={0} reagent count={1} search queue={2}", runningThreads, costSortedReagents.Count, searchQueue.Count);
 
            }
 
            runningThreads = 0; // presumably!
 

	
 
            int threadCount = Math.Min(costSortedReagents.Count, searchQueue.Count);
 
            int threadCount = 1; // Math.Min(costSortedReagents.Count, searchQueue.Count);
 
            if (threadCount == 0)
 
            {
 
                if (Finished != null)
 
                {
 
                    Finished(this, null);
 
                }
 
            }
 
            generatorThreads.Clear();
 
            for (int i = 0; i < threadCount; ++i)
 
            {
 
                Thread thr = new Thread(new ThreadStart(this.Generate));
 
                generatorThreads.Add(thr);
 
            }
 
            foreach (Thread thr in generatorThreads)
 
            {
 
                thr.Start();
 
            }
 
        }
 

	
 
        public bool SaveState(string file)
 
        {
 
            if (running)
 
            {
 
                // can't save state while running
 
                return false;
 
            }
 

	
 
            using (StreamWriter writer = new StreamWriter(file, false))
 
            {
 
                writer.WriteLine("MaxReagents: {0}", maxReagents);
 
                writer.WriteLine("FullQuantityDepth: {0}", fullQuantityDepth);
 
                writer.WriteLine("FullQuantity: {0}", fullQuantity);
 
                writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
                writer.WriteLine("FullQuantityDepth: {0}", FullQuantityDepth);
 
                writer.WriteLine("FullQuantity: {0}", FullQuantity);
 
                writer.WriteLine("TotalReagents: {0}", totalReagents);
 
                writer.WriteLine("RecipeCount: {0}", recipeCount);
 
                writer.WriteLine("SearchType: {0}", Mode.ToString());
 
                foreach (KeyValuePair<string, PaintRecipe> pair in recipes)
 
                {
 
                    PaintRecipe recipe = pair.Value;
 
                    string colorName = Palette.FindNearest(recipe.ReactedColor);
 
                    writer.WriteLine("BeginRecipe: {0}", colorName);
 
                    foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients)
 
                    {
 
                        writer.WriteLine("Ingredient: {0}={1}", ingredient.name, ingredient.quantity);
 
                    }
 
                    writer.WriteLine("EndRecipe: {0}", colorName);
 
                }
 
                writer.WriteLine("SearchNodes: {0}", searchQueue.Count);
 
                foreach (SearchNode node in searchQueue)
 
                foreach (RecipeSearchNode node in searchQueue)
 
                {
 
                    node.SaveState(writer);
 
               }
 
            }
 
            return true;
 
        }
 

	
 
        static Regex keyValueRegex = new Regex(@"(?<key>\w+)\:\s*(?<value>.+)\s*");
 
        static Regex ingredientRegex = new Regex(@"(?<ingredient>(\w+\s)*\w+)\s*=\s*(?<quantity>\d)\s*");
 
        public bool LoadState(string file)
 
        {
 
            // cannot be running, and reactions must be set
 
            if (running)
 
            {
 
                return false;
 
            }
 

	
 
            InitSortedReagents();
 
            bool success = true;
 

	
 
            PaintRecipe currentRecipe = null;
 
            Match match;
 
            string line;
 
            using (StreamReader reader = new StreamReader(file, false))
 
            {
 
                while (success && ((line = reader.ReadLine()) != null))
 
                {
 
                    match = keyValueRegex.Match(line);
 
                    if (match.Success)
 
                    {
 
                        string value = match.Groups["value"].Value;
 
                        switch(match.Groups["key"].Value)
 
                        {
 
                            case "MaxReagents":
 
                                maxReagents = uint.Parse(value);
 
                                MaxReagents = uint.Parse(value);
 
                                break;
 
                            case "FullQuantityDepth":
 
                                fullQuantityDepth = uint.Parse(value);
 
                                FullQuantityDepth = uint.Parse(value);
 
                                break;
 
                            case "FullQuantity":
 
                                fullQuantity = uint.Parse(value);
 
                                FullQuantity = uint.Parse(value);
 
                                break;
 
                            case "TotalReagents":
 
                                totalReagents = uint.Parse(value);
 
                                break;
 
                            case "RecipeCount":
 
                                recipeCount = int.Parse(value);
 
                                if (!ulong.TryParse(value, out recipeCount))
 
                                {
 
                                    // must have rolled to negative - try as an int and convert
 
                                    int recipeCountInt = int.Parse(value);
 
                                    recipeCount = (ulong)((long)recipeCountInt & 0x00000000ffffffffL);
 
                                }
 
                                break;
 
                            case "BeginRecipe":
 
                                currentRecipe = new PaintRecipe();
 
                                currentRecipe.Reactions = reactions;
 
                                break;
 
                            case "EndRecipe":
 
                                if (currentRecipe != null)
 
                                {
 
                                    PaintColor color = currentRecipe.ReactedColor;
 
                                    uint cost = currentRecipe.Cost;
 
                                    recipes[color.Name] = currentRecipe; // replace
 
                                    recipeCosts[color.Name] = cost;
 
                                    currentRecipe = null;
 
                                }
 
                                break;
 
                            case "Ingredient":
 
                                if (currentRecipe != null)
 
                                {
 
                                    Match ingredientMatch = ingredientRegex.Match(match.Groups["value"].Value);
 
                                    if (ingredientMatch.Success)
 
                                    {
 
                                        uint quantity = uint.Parse(ingredientMatch.Groups["quantity"].Value);
 
                                        currentRecipe.AddReagent(ingredientMatch.Groups["ingredient"].Value, quantity);
 
                                    }
 
                                    else
 
                                    {
 
                                        success = false;
 
                                    }
 
                                }
 
                                break;
 
                            case "SearchNodes":
 
                                int nodeCount = int.Parse(match.Groups["value"].Value);
 
                                for (int i = 0; i < nodeCount; ++i)
 
                                {
 
                                    SearchNode node = new SearchNode(costSortedReagents);
 
                                    RecipeSearchNode node = new RecipeSearchNode(costSortedReagents);
 
                                    node.FullQuantity = FullQuantity;
 
                                    node.FullQuantityDepth = FullQuantityDepth;
 
                                    node.MaxReagents = MaxReagents;
 
                                    node.MaxQuantity = MaxQuantity;
 
                                    success = success && node.LoadState(reader);
 
                                    if (success)
 
                                    {
 
                                        searchQueue.Enqueue(node);
 
                                    }
 
                                }
 
                                break;
 
                            case "SearchType":
 
                                Mode = (SearchType)Enum.Parse(typeof(SearchType), match.Groups["value"].Value);
 
                                break;
 
                            default:
 
                                success = false;
 
                                break;
 
                        }
 
                    }
 
                    else
 
                    {
 
                        success = false;
 
                        break;
 
                    }
 
                }
 
                return success;
 
            }
 
        }
 

	
 
        private void Generate()
 
        {
 
            SearchNode node;
 
            RecipeSearchNode node;
 

	
 
            lock(workerLock)
 
            {
 
                ++runningThreads;
 
            }
 

	
 
            bool ok = true;
 
            do
 
            {
 
                lock (workerLock)
 
                {
 
                    ok = searchQueue.TryDequeue(out node);
 
                }
 
                if (ok)
 
                {
 
                    uint targetQuantity = node.MaxQuantity + 1;
 
                    do {
 
                        --targetQuantity;
 
                        node.InitForQuantity(targetQuantity);
 
                    } while (targetQuantity > 10 && (node.CurrentTargetQuantity != node.UsedQuantity));
 

	
 
                    while ((ok = Iterate(node)) && !requestCancel)
 
                    if (Mode == SearchType.DEPTH_FIRST)
 
                    {
 
                        if (Progress != null)
 
                        uint targetQuantity = (node.ReagentCount <= FullQuantityDepth) ? ((uint)node.ReagentCount * FullQuantity) : node.MaxQuantity + 1;
 
                        do {
 
                            --targetQuantity;
 
                            node.InitForQuantity(targetQuantity);
 
                        } while (targetQuantity > 10 && (node.CurrentTargetQuantity != node.UsedQuantity));
 
    
 
                        while ((ok = IterateDepthFirst(node)) && !requestCancel)
 
                        {
 
                            Progress(this, null);
 
                            if (Progress != null)
 
                            {
 
                                Progress(this, null);
 
                            }
 
                        }
 
                    }
 
                    else
 
                    {
 
                        // breadth-first search
 
                        uint targetQuantity = 9;
 
                        uint quantityLimit = (node.ReagentCount <= FullQuantityDepth) ? (FullQuantity * (uint)node.ReagentCount) : node.MaxQuantity;
 
                        do {
 
                            ++targetQuantity;
 
                            node.InitForQuantity(targetQuantity);
 
                        } while ((targetQuantity <= quantityLimit) && (node.CurrentTargetQuantity != node.UsedQuantity));
 
    
 
                        while ((ok = IterateBreadthFirst(node)) && !requestCancel)
 
                        {
 
                            if (Progress != null)
 
                            {
 
                                Progress(this, null);
 
                            }
 
                        }
 
                    }
 
                    if (ok)
 
                    {
 
                        // stopped because cancel was requested - requeue the node in its current state for resume
 
                        searchQueue.Enqueue(node);
 
                    }
 
                }
 
            } while (!requestCancel && ok);
 

	
 
            bool done = false;
 
            lock(workerLock)
 
            {
 
                --runningThreads;
 
                //generatorThreads.Remove(Thread.CurrentThread);
 

	
 
                done = (runningThreads == 0);
 
            }
 
            if (done)
 
            {
 
                running = false;
 
                requestCancel = false;
 
                if (Finished != null)
 
                {
...
 
@@ -861,64 +542,66 @@ namespace DesertPaintLab
 
                        // This would be an error!
 
                        recipeCosts.Add(colorName, recipe.Cost);
 
                        recipes.Add(colorName, new PaintRecipe(recipe));
 
                        if (NewRecipe != null)
 
                        {
 
                            NewRecipeEventArgs args = new NewRecipeEventArgs(colorName, recipe);
 
                            NewRecipe(this, args);
 
                        }
 
                    }
 
                }
 
            }
 
            //else
 
            //{
 
            //    string msg = String.Format("Recipe is invalid ({0} ingredients)\n", recipe.Ingredients.Count);
 
            //    foreach (PaintRecipe.RecipeIngredient ingr in recipe.Ingredients)
 
            //    {
 
            //        msg += String.Format("    -> {0} {1}", ingr.quantity, ingr.name);
 
            //    }
 
            //    lock (workerLock) {
 
            //        Console.WriteLine(msg);
 
            //    }
 
            //}
 
        }
 

	
 
        private bool Iterate(SearchNode node)
 
        private bool IterateDepthFirst(RecipeSearchNode node)
 
        {
 
            TestCurrentRecipe(node);
 

	
 
            // pick recipe quantities at current recipe ingredients/size
 
            if (NextRecipe(node))
 
            {
 
                lock(workerLock)
 
                {
 
                    ++recipeCount;
 
                }
 
                //System.Console.WriteLine("Found next recipe at size {0} qty {1}", node.Reagents.Count, node.CurrentTargetQuantity);
 
                return true;
 
            }
 

	
 
            if (NextRecipeSize(node))
 
            {
 
                //System.Console.WriteLine("Found next recipee size {0}", node.CurrentTargetQuantity);
 
                //System.Console.WriteLine("Found next recipe size {0}", node.CurrentTargetQuantity);
 
                return true;
 
            }
 

	
 
            // Search for next ingredient combo - all quantity combos for previous were searched
 
            //System.Console.WriteLine("Finding next ingredient combo");
 
            do
 
            {
 
                if (!node.AddNextReagent())
 
                {
 
                    while ((node.ReagentCount > node.InitialCount) && (node.LastReagent == (totalReagents-1)))
 
                    {
 
                        node.RemoveLastReagent();
 
                    }
 
                    if (node.ReagentCount == node.InitialCount)
 
                    {
 
                        // done
 
                        return false;
 
                    }
 
                    uint nextReagent = node.NextFreeReagent(node.LastReagent);
 
                    while ((node.ReagentCount > node.InitialCount) && (nextReagent >= totalReagents))
 
                    {
 
                        // No more reagents to try at this level
 
                        node.RemoveLastReagent();
 
                        if (node.ReagentCount > node.InitialCount)
...
 
@@ -929,126 +612,268 @@ namespace DesertPaintLab
 
                    if (node.ReagentCount == node.InitialCount)
 
                    {
 
                        // done
 
                        return false;
 
                    }
 
                    node.ReplaceLastReagent(nextReagent);
 
                }
 
            } while (node.MaxQuantity < (10 + node.CatalystCount));
 
            node.InitForQuantity(node.MaxQuantity);
 

	
 
            //string outStr = "{0} : {1} : ";
 
            //for (int i = 0; i < currentReagents.Count; ++i)
 
            //{
 
            //    Reagent reagent = costSortedReagents[(int)currentReagents[i]];
 
            //    if (i > 0)
 
            //    {
 
            //        outStr += ", ";
 
            //    }
 
            //    outStr += reagent.Name + " (" + reagent.Cost + ")";
 
            //}
 
            //Console.WriteLine(outStr, currentReagents.Count, recipeCount);
 
            return true;
 
        }
 

	
 
        private bool NextRecipe(SearchNode node)
 
        private bool IterateBreadthFirst(RecipeSearchNode node)
 
        {
 
            // First, run the current recipe
 
            // pick recipe quantities at current recipe ingredients/size
 
            TestCurrentRecipe(node);
 
            lock(workerLock)
 
            {
 
                ++recipeCount;
 
            }
 

	
 
            // search all quantities of current recipe
 
            if (NextRecipe(node))
 
            {
 
                //System.Console.WriteLine("Found next recipe at size {0} qty {1}", node.ReagentCount, node.CurrentTargetQuantity);
 
                return true;
 
            }
 

	
 
            // Try next quantity
 
            uint newQuantity;
 
            uint quantityLimit = ((uint)node.ReagentCount <= FullQuantityDepth) ? ((uint)node.ReagentCount * FullQuantity) : node.MaxQuantity;
 
            do {
 
                newQuantity = node.CurrentTargetQuantity + 1;
 
                //Console.WriteLine("Try quantity {0}", newQuantity);
 
                if (newQuantity <= quantityLimit)
 
                {
 
                    node.InitForQuantity(newQuantity);
 
                    if (node.CurrentTargetQuantity <= node.UsedQuantity)
 
                    {
 
                        if (log != null) { lock(log) { log.WriteLine("Update quantity to {0}", node.CurrentTargetQuantity); } }
 
                        return true;
 
                    }
 
                }
 
            } while (newQuantity < quantityLimit);
 

	
 
            // search all variants at this depth of recipe
 
            // increase recipe depth
 

	
 
            // next reagent in last position
 
            // if at end, pop reagent
 
            //Console.WriteLine("Finding new recipe after quantity {0}/{1} used {2}", newQuantity, node.MaxQuantity, node.UsedQuantity);
 
            node.InitForQuantity(10+node.CatalystCount); // reset quantity
 
            int currentDepth = node.ReagentCount;
 
            bool recipeFound = false;
 
            uint nextReagent;
 
            do {
 
                //Console.WriteLine("Current depth: {0}/{1}", currentDepth, node.MaxReagents);
 
                do {
 
                    recipeFound = false;
 
                    // back out until we find a node that can be incremented
 
                    if (currentDepth > node.InitialCount)
 
                    {
 
                        while (node.ReagentCount > node.InitialCount)
 
                        {
 
                            if (node.LastReagent < (totalReagents - 1))
 
                            {
 
                                nextReagent = node.NextFreeReagent(node.LastReagent);
 
                                if (nextReagent < totalReagents)
 
                                {
 
                                    //Console.WriteLine("Replace last reagent with {0}", nextReagent);
 
                                    node.ReplaceLastReagent(nextReagent);
 
                                    break;
 
                                }
 
                                else
 
                                {
 
                                    // shouldn't happen
 
                                    //Console.WriteLine("No available reagents at depth {0}!", node.ReagentCount);
 
                                    node.RemoveLastReagent();
 
                                    if (node.ReagentCount == node.InitialCount)
 
                                    {
 
                                        // just popped the last reagent at the top level
 
                                        ++currentDepth;
 
                                        if (log != null) { lock(log) { log.WriteLine("Increased depth to {0}/{1}", currentDepth, node.MaxReagents); } }
 
                                    }
 
                                }
 
                            }
 
                            else
 
                            {
 
                                //Console.WriteLine("Pop last reagent");
 
                                node.RemoveLastReagent();
 
                                if (node.ReagentCount == node.InitialCount)
 
                                {
 
                                    // just popped the last reagent at the top level
 
                                    ++currentDepth;
 
                                    if (log != null) { lock(log) { log.WriteLine("Increased depth to {0}/{1} [pop last reagent at top level]", currentDepth, node.MaxReagents); } }
 
                                }
 
                            }
 
                        }
 
                        // fill in the nodes up to the current depth
 
                        if (node.ReagentCount >= node.InitialCount)
 
                        {
 
                            recipeFound = true;
 
                            while (node.ReagentCount < currentDepth)
 
                            {
 
                                if (! node.AddNextReagent())
 
                                {
 
                                    if (log != null) { lock(log) { log.WriteLine("Failed to reagent {0}/{1}", node.ReagentCount+1, currentDepth); } }
 
                                    recipeFound = false;
 
                                }
 
                            }
 
                        }
 
                    }
 
                    //Console.WriteLine("Catalysts: {0} Reagents: {1} Initial: {2}", node.CatalystCount, node.ReagentCount, node.InitialCount);
 
                } while ((node.CatalystCount >= node.ReagentCount) && (node.ReagentCount >= node.InitialCount)); // make sure to skip all-catalyst combinations
 
                if (recipeFound)
 
                {
 
                    break;
 
                }
 
                else
 
                {
 
                    ++currentDepth;
 
                    if (log != null) { lock(log) { log.WriteLine("Increased depth to {0}/{1} [no recipe]", currentDepth, node.MaxReagents); } }
 
                }
 
            } while (currentDepth <= node.MaxReagents);
 
            if (recipeFound)
 
            {
 
                node.InitForQuantity(10+node.CatalystCount); // minimum quantity for this recipe
 
                if (node.TestRecipe == null)
 
                {
 
                    node.TestRecipe = new PaintRecipe();
 
                    node.TestRecipe.Reactions = reactions;
 
                }
 
                node.TestRecipe.Clear();
 
                for (int i = 0; i < node.ReagentCount; ++i)
 
                {
 
                    node.TestRecipe.AddReagent(node.Reagent(i).Name, node.CurrentWeights[i]);
 
                }
 
                if (log != null) { 
 
                    string combo = "";
 
                    foreach (PaintRecipe.RecipeIngredient ingr in node.TestRecipe.Ingredients)
 
                    {
 
                        combo += " " + ingr.name;
 
                    }
 
                    lock(log) { log.WriteLine("New ingredients: " + combo); }
 
                }
 
            }
 

	
 
            return recipeFound;
 
        }
 

	
 
        private void TestCurrentRecipe(RecipeSearchNode node)
 
        {
 
            if (node.TestRecipe == null)
 
            {
 
                node.TestRecipe = new PaintRecipe();
 
                node.TestRecipe.Reactions = reactions;
 
            }
 
            node.TestRecipe.Clear();
 
            for (int i = 0; i < node.ReagentCount; ++i)
 
            {
 
                node.TestRecipe.AddReagent(node.Reagent(i).Name, node.CurrentWeights[i]);
 
            }
 
            AddCheapestRecipe(node.TestRecipe);
 
            
 
            //if (log != null) { lock(log) { log.WriteLine("Tested recipe: {0}", node.TestRecipe); } }
 
        }
 

	
 
        private bool NextRecipe(RecipeSearchNode node)
 
        {
 
            // check for the next recipe
 
            uint remainingWeight = node.CurrentTargetQuantity - node.CatalystCount;
 
            if (remainingWeight < 10)
 
            {
 
                // not possible to make a valid recipe
 
                Console.WriteLine("Insufficient remaining weight");
 
                return false;
 
            }
 
            //uint remainingReagents = (uint)node.Reagents.Count - node.CatalystCount;
 

	
 
            uint depth = (uint)node.ReagentCount;
 
            uint weightToConsume = 0;
 
            uint spaceBelow = 0;
 
            int reagentsBelow = 0;
 
            for (int i = (int)depth-1 ; i >= 0; --i)
 
            {
 
                uint currentWeight = node.CurrentWeights[i];
 

	
 
                if ((spaceBelow >= (weightToConsume+1)) && (currentWeight > 1))
 
                {
 
                    // reduce this node by 1, allocate remaining weight to reagents below it
 
                    node.SetWeight(i, currentWeight-1);
 
                    weightToConsume += 1;
 
                    for (int j = i+1; j < depth; ++j)
 
                    {
 
                        --reagentsBelow;
 
                        Reagent reagent = node.Reagent(j);
 
                        uint allocated = (uint)Math.Min(reagent.IsCatalyst ? 1 : (depth <= fullQuantityDepth ? fullQuantity : reagent.RecipeMax), weightToConsume - reagentsBelow);
 
                        uint allocated = (uint)Math.Min(reagent.IsCatalyst ? 1 : (depth <= FullQuantityDepth ? FullQuantity : reagent.RecipeMax), weightToConsume - reagentsBelow);
 
                        if (allocated > 100)
 
                        {
 
                            Console.WriteLine("ACK: allocated = {0}", allocated);
 
                        }
 
                        node.SetWeight(j, allocated);
 
                        weightToConsume -= allocated;
 
                    }
 
                    break;
 
                }
 
                else
 
                {
 
                    Reagent reagent = node.Reagent(i);
 
                    spaceBelow += (reagent.IsCatalyst ? 1 : (depth <= fullQuantityDepth ? fullQuantity : reagent.RecipeMax));
 
                    spaceBelow += (reagent.IsCatalyst ? 1 : (depth <= FullQuantityDepth ? FullQuantity : reagent.RecipeMax));
 
                    weightToConsume += currentWeight;
 
                    ++reagentsBelow;
 
                }
 
            }
 

	
 
            //int recipeWeight = 0;
 
            //foreach (int weight in node.CurrentWeights)
 
            //{
 
            //    recipeWeight += weight;
 
            //}
 
            //if ((weightToConsume != 0) || (recipeWeight != node.CurrentTargetQuantity))
 
            //{
 
            //    Console.WriteLine("Failed recipe with leftover weight {0} ({1}/{2}):", weightToConsume, recipeWeight, node.CurrentTargetQuantity);
 
            //    for (int i = 0; i < node.Reagents.Count; ++i)
 
            //    {
 
            //        Console.WriteLine("   > {0} {1}", node.Reagent(i).Name, node.CurrentWeights[i]);
 
            //    }
 
            //}
 
            
 
            return (weightToConsume == 0);
 
        }
 

	
 
        private bool NextRecipeSize(SearchNode node)
 
        private bool NextRecipeSize(RecipeSearchNode node)
 
        {
 
            uint newQuantity = node.CurrentTargetQuantity - 1;
 
            if (newQuantity < (10 + node.CatalystCount))
 
            {
 
                return false;
 
            }
 

	
 
            node.InitForQuantity(newQuantity);
 
            if (node.CurrentTargetQuantity > node.UsedQuantity)
 
            {
 
                return false;
 
            }
 

	
 
            return true;
 
        }
 

	
 
        public void Wait()
 
        {
 
            if (generatorThreads.Count > 0)
 
            {
 
                foreach (Thread thr in generatorThreads)
 
                {
 
                    thr.Join();
 
                }
RecipeGeneratorWindow.cs
Show inline comments
 
/*
 
 * Copyright (c) 2015, Jason Maltzen
 

	
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 
 of this software and associated documentation files (the "Software"), to deal
 
 in the Software without restriction, including without limitation the rights
 
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
 copies of the Software, and to permit persons to whom the Software is
 
 furnished to do so, subject to the following conditions:
 

	
 
 The above copyright notice and this permission notice shall be included in
 
 all copies or substantial portions of the Software.
 

	
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
 THE SOFTWARE.
 
*/
 

	
 
using System;
 
using System.IO;
 
using System.Collections.Generic;
 
using System.Collections.Concurrent;
 

	
 
namespace DesertPaintLab
 
{
 
    public partial class RecipeGeneratorWindow : Gtk.Window
 
    {
 
        RecipeGenerator generator;
 
        PlayerProfile profile;
 
        bool canceling = false;
 
        bool running = false;
 
        bool pauseForCheckpoint = false;
 

	
 
        const long RECIPE_SAVE_INTERVAL = 30000; // msec between saving recipes
 
        const long CHECKPOINT_INTERVAL = 17000; // msec between saving out generator state
 
        const string STATE_FILE = "dp_generator_state";
 

	
 
        static Gtk.ListStore colorStore = new Gtk.ListStore(typeof(string));
 

	
 
        List<KeyValuePair<string, string>> missingReactions = new List<KeyValuePair<string, string>>();
 

	
 
        long lastProgressUpdate;
 
        long lastStatusUpdate;
 
        long lastProfileSave;
 
        long lastCheckpoint;
 

	
 
        static public Gtk.ListStore RecipeModel
 
        {
 
            get
 
            {
 
                return colorStore;   
 
            }
 
        }
 

	
 
        Gtk.ThreadNotify notifyFinished;
 
        Gtk.ThreadNotify notifyProgress;
 
        Gtk.ThreadNotify notifyNewRecipe;
 

	
 
        ConcurrentQueue<PaintRecipe> pendingNewRecipes = new ConcurrentQueue<PaintRecipe>();
 

	
 
        Gtk.ListStore reagentListStore;
 
        // end reagent view
 

	
 
        public RecipeGeneratorWindow(PlayerProfile profile) : base(Gtk.WindowType.Toplevel)
 
        {
 
            this.profile = profile;
 
            this.Build();
 
            maxIngredientsSpinButton.Value = 5; // TODO: read/save profile info
 
            maxRecipeSpinButton.Value = 20; // TODO: read/save profile info
 
            fullQuantitySpinButton.Value = 20; // TODO: read/save profile info
 
            fullQuantityDepthSpinButton.Value = 4; // TODO: read/save profile info
 

	
 
            fullQuantityDepthSpinButton.SetRange(0, ReagentManager.Names.Count);
 
            maxIngredientsSpinButton.SetRange(0, ReagentManager.Names.Count);
 

	
 

	
 
            Gtk.TreeViewColumn recipeColorColumn = new Gtk.TreeViewColumn();
 
            Gtk.CellRendererText recipeColumnCell = new Gtk.CellRendererText();
 
            recipeColorColumn.PackStart(recipeColumnCell, true);       
 
            recipeColorColumn.Title = "Color";
 

	
 
            recipeList.AppendColumn(recipeColorColumn);
 
            recipeColorColumn.AddAttribute(recipeColumnCell, "text", 0);
 

	
 
            colorStore.Clear();
 

	
 
            colorStore.SetSortColumnId(0, Gtk.SortType.Ascending);
 
            recipeList.Model = RecipeModel;
 

	
 
            recipeList.Selection.Changed += OnColorSelected;
 

	
 
            recipeIngredientsView.AppendColumn("Quantity", new Gtk.CellRendererText(), "text", 0);
 
            recipeIngredientsView.AppendColumn("Ingredient", new Gtk.CellRendererText(), "text", 1);
 
            recipeIngredientsView.Model = new Gtk.ListStore(typeof(string), typeof(string));
 

	
 
            profile.LoadRecipes();
 

	
 
            // init UI
 
            foreach (KeyValuePair<string, PaintRecipe> pair in profile.Recipes)
 
            {
 
                if (pair.Value.IsValid)
 
                {
 
                    string colorName = pair.Key;
 
                    colorStore.AppendValues(colorName);
 
                }
 
            }
 

	
 
            canceling = false;
 
            running = false;
 
            pauseForCheckpoint = false;
 

	
 
            generator = new RecipeGenerator(profile.Reactions);
 
            generator.InitRecipes(profile.Recipes);
 

	
 
            generator.Progress += OnProgress;
 
            generator.Finished += OnFinished;
 
            generator.NewRecipe += OnNewRecipe;
 

	
 
            string stateFile = System.IO.Path.Combine(profile.Directory, STATE_FILE);
 
            if (System.IO.File.Exists(stateFile))
 
            {
 
                generator.LoadState(stateFile);
 
                if (generator.CanResume)
 
                {
 
                    beginButton.Label = "Restart";
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 

	
 
                    maxRecipeSpinButton.Value = Math.Max(generator.MaxQuantity, 14); //
 
                    fullQuantitySpinButton.Value = generator.FullQuantity; // TODO: read/save profile info
 
                    fullQuantityDepthSpinButton.Value = generator.FullQuantityDepth; // TODO: read/save profile info
 
                    maxIngredientsSpinButton.Value = generator.MaxReagents;
 
                }
 
            }
 
            generator.Log = System.IO.Path.Combine(profile.Directory, "dp_log.txt");
 
            countLabel.Text = String.Format("{0} / {1}", profile.RecipeCount, Palette.Count);
 

	
 
            Destroyed += OnDestroyed;
 

	
 
            notifyFinished = new Gtk.ThreadNotify(new Gtk.ReadyEvent(HandleFinished));
 
            notifyProgress = new Gtk.ThreadNotify(new Gtk.ReadyEvent(HandleProgress));
 
            notifyNewRecipe = new Gtk.ThreadNotify(new Gtk.ReadyEvent(HandleNewRecipe));
 

	
 
            // initialize reagent list
 
 
 
            // Add the columns to the TreeView
 
            Gtk.TreeViewColumn reagentEnabledColumn = new Gtk.TreeViewColumn ();
 
            reagentEnabledColumn.Title = "Enabled";
 
            Gtk.CellRendererToggle reagentEnabledCell = new Gtk.CellRendererToggle ();
 
            reagentEnabledCell.Activatable = true;
 
            reagentEnabledCell.Sensitive = true;
 
            reagentEnabledCell.Mode = Gtk.CellRendererMode.Activatable;
 
            reagentEnabledCell.Toggled += new Gtk.ToggledHandler(OnReagentEnableToggled);
 
            reagentEnabledColumn.PackStart (reagentEnabledCell, true);
 
            //reagentEnabledColumn.AddAttribute(reagentEnabledCell, "active", 0);
 

	
 
            Gtk.TreeViewColumn reagentNameColumn = new Gtk.TreeViewColumn ();
 
            reagentNameColumn.Title = "Ingredient";
 
            Gtk.CellRendererText reagentNameCell = new Gtk.CellRendererText ();
 
            reagentNameColumn.PackStart (reagentNameCell, true);
 
            reagentNameColumn.AddAttribute(reagentNameCell, "text", 1);
 
            reagentNameColumn.Expand = true;
 

	
 
            Gtk.TreeViewColumn reagentCostColumn = new Gtk.TreeViewColumn ();
 
            reagentCostColumn.Title = "Cost";
 
            Gtk.CellRendererText reagentCostCell = new Gtk.CellRendererText ();
 
            reagentCostCell.Edited += OnReagentCostChanged;
 
            reagentCostCell.Editable = true;
 
            reagentCostCell.Sensitive = true;
 
            reagentCostColumn.PackStart (reagentCostCell, true);
 
            //reagentCostColumn.AddAttribute(reagentCostCell, "text", 0);
 

	
 
            Gtk.TreeViewColumn reagentMaxColumn = new Gtk.TreeViewColumn ();
 
            reagentMaxColumn.Title = "Max";
 
            Gtk.CellRendererText reagentMaxCell = new Gtk.CellRendererText ();
 
            reagentMaxCell.Edited += OnReagentQuantityChanged;
 
            reagentMaxCell.Editable = true;
 
            reagentMaxCell.Sensitive = true;
 
            reagentMaxColumn.PackStart (reagentMaxCell, true);
 
            //reagentMaxColumn.AddAttribute(reagentMaxCell, "text", 0);
 

	
 
            reagentListStore = new Gtk.ListStore(typeof(Reagent), typeof(string), typeof(Reagent), typeof(Reagent));
 
            foreach (string reagentName in ReagentManager.Names)
 
            {
 
                Reagent reagent = ReagentManager.GetReagent(reagentName);
 
                reagentListStore.AppendValues(reagent, reagentName); // , reagent, reagent);
 
            }
 

	
 
            reagentEnabledColumn.SetCellDataFunc (reagentEnabledCell, new Gtk.TreeCellDataFunc (RenderReagentToggle));
 
            reagentCostColumn.SetCellDataFunc (reagentCostCell, new Gtk.TreeCellDataFunc (RenderReagentCost));
 
            reagentMaxColumn.SetCellDataFunc (reagentMaxCell, new Gtk.TreeCellDataFunc (RenderReagentQuantity));
 

	
 
            // Assign the model to the TreeView
 
            reagentListView.Model = reagentListStore;
 

	
 
            reagentListView.AppendColumn(reagentEnabledColumn);
 
            reagentListView.AppendColumn(reagentNameColumn);
 
            reagentListView.AppendColumn(reagentCostColumn);
 
            reagentListView.AppendColumn(reagentMaxColumn);
 
            
 
            ShowAll();
 
        }
 

	
 
        protected void OnMaxIngredientsChanged(object sender, EventArgs e)
 
        {
 
            // TODO: save profile setting
 
            // TODO: no longer permit resume
 
        }
 

	
 
        protected void OnMaxRecipeChanged(object sender, EventArgs e)
 
        {
 
            // TODO: save profile setting
 
            // TODO: no longer permit resume
 
        }
 

	
 
        protected void OnFullQuantityDepthChanged(object sender, EventArgs e)
 
        {
 
            // TODO: save profile setting
 
            // TODO: no longer permit resume
 
        }
 

	
 
        protected void OnFullQuantityChanged(object sender, EventArgs e)
 
        {
 
            // TODO: save profile setting
 
            // TODO: no longer permit resume
 
        }
 

	
 
        protected void OnBegin(object sender, EventArgs e)
 
        {
 
            maxIngredientsSpinButton.Sensitive = false;
 
            ExportToWikiAction.Sensitive = false;
 
            IngredientsAction.Sensitive = false;
 
            maxRecipeSpinButton.Sensitive = false;
 
            beginButton.Sensitive = false; // TODO: change to "pause"?
 
            stopResumeButton.Sensitive = true;
 
            fullQuantitySpinButton.Sensitive = false;
 
            fullQuantityDepthSpinButton.Sensitive = false;
 
            reagentListView.Sensitive = false;
 

	
 
            countLabel.Text = String.Format("{0} / {1}", profile.RecipeCount, Palette.Count);
 

	
 
            // TODO: hook up event notifications
 
            // - progress
 
            // - complete
 
            // - new recipe / recipe update
 

	
 
            // Total recipe search count
 
            //int current = ReagentManager.Names.Count;
 
            //long recipePermutations = 1;
 
            //for (int i = 0; i < maxIngredientsSpinButton.ValueAsInt; ++i)
 
            //{
 
            //    recipePermutations *= current;
 
            //    --current;
 
            //}
 
            //System.Console.WriteLine("Will search {0} reagent permutations.", recipePermutations);
 

	
 
            lastProgressUpdate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
 
            lastStatusUpdate = lastProgressUpdate;
 

	
 
            lastProfileSave = lastProgressUpdate;
 
            lastCheckpoint = lastProgressUpdate;
 

	
 
            running = true;
 
            canceling = false;
 
            pauseForCheckpoint = false;
 
            stopResumeButton.Label = "Pause";
 

	
 
            generator.BeginRecipeGeneration((uint)maxRecipeSpinButton.ValueAsInt, (uint)maxIngredientsSpinButton.ValueAsInt, (uint)fullQuantityDepthSpinButton.ValueAsInt, (uint)fullQuantitySpinButton.ValueAsInt);
 
        }
 

	
 
        protected void OnStopResume(object sender, EventArgs e)
 
        {
 
            if (generator != null)
 
            {
 
                if (running)
 
                {
 
                    canceling = true;
 
                    pauseForCheckpoint = false;
 
                    generator.Stop();
 
                }
 
                else
 
                {
 
                    // Resume previous run
 
                    ExportToWikiAction.Sensitive = false;
 
                    IngredientsAction.Sensitive = false;
 
                    reagentListView.Sensitive = false;
 
                    maxIngredientsSpinButton.Sensitive = false;
 
                    maxRecipeSpinButton.Sensitive = false;
 
                    beginButton.Sensitive = false;
 
                    stopResumeButton.Sensitive = true;
 
                    fullQuantitySpinButton.Sensitive = false;
 
                    fullQuantityDepthSpinButton.Sensitive = false;
 
                    reagentListView.Sensitive = false;
 

	
 
                    lastProgressUpdate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
 
                    lastStatusUpdate = lastProgressUpdate;
 
                    lastProfileSave = lastProgressUpdate;
 
                    lastCheckpoint = lastProgressUpdate;
 

	
 
                    canceling = false;
 
                        pauseForCheckpoint = false;
 
                    pauseForCheckpoint = false;
 
                    running = true;
 
        
 
                    stopResumeButton.Label = "Pause";
 
                    generator.ResumeRecipeGeneration();
 
                }
 
            }
 
        }
 

	
 
        private void HandleFinished()
 
        {
 
            generator.Wait();
 
            if (pauseForCheckpoint)
 
            {
 
                pauseForCheckpoint = false;
 
                generator.SaveState(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
                generator.ResumeRecipeGeneration();
 
            }
 
            else
 
            {
 
                running = false;
 
                beginButton.Sensitive = true;
 
                ExportToWikiAction.Sensitive = true;
 
                IngredientsAction.Sensitive = true;
 
                stopResumeButton.Sensitive = false;
 
                maxIngredientsSpinButton.Sensitive = true;
 
                maxRecipeSpinButton.Sensitive = true;
 
                fullQuantitySpinButton.Sensitive = true;
 
                fullQuantityDepthSpinButton.Sensitive = true;
 
                reagentListView.Sensitive = false;
 
                //generator = null; // don't. Hang on to generator for resume.
 
                profile.SaveRecipes();
 
                if (canceling)
 
                {
 
                    generator.SaveState(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
                    stopResumeButton.Label = "Resume";
 
                    stopResumeButton.Sensitive = true;
 
                    beginButton.Label = "Restart";
 
                }
 
                else
 
                {
 
                    System.IO.File.Delete(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
                }
 
            }
 
        }
 

	
 
        protected void OnFinished(object sender, EventArgs args)
 
        {
 
            notifyFinished.WakeupMain();
 
            //Gtk.Application.Invoke(delegate {
 
            //    HandleFinished();
 
            //});
 
        }
 

	
...
 
@@ -345,82 +430,179 @@ namespace DesertPaintLab
 
            if (delta > CHECKPOINT_INTERVAL)
 
            {
 
                lastCheckpoint = progressTime;
 
                pauseForCheckpoint = true;
 
                generator.Stop();
 
            }
 
        }
 

	
 
        protected void OnProgress(object sender, EventArgs args)
 
        {
 
            notifyProgress.WakeupMain();
 
        }
 

	
 
        protected void OnColorSelected(object o, EventArgs args)
 
        {
 
            Gtk.TreeModel model;
 
            Gtk.TreeIter iter;
 
            Gtk.TreeSelection selection = recipeList.Selection;
 
            if ((selection != null) && selection.GetSelected(out model, out iter))
 
            {
 
                string colorName = (string)colorStore.GetValue(iter, 0);
 
                PaintRecipe recipe;
 
                if (profile.Recipes.TryGetValue(colorName, out recipe))
 
                {
 
                    foreach (Gtk.Widget child in recipeListBox.AllChildren)
 
                    {
 
                        recipeListBox.Remove(child);
 
                    }
 
                    Gtk.ListStore store = (Gtk.ListStore)recipeIngredientsView.Model;
 
                    store.Clear();
 
                    if (recipe.CheckMissingReactions(ref missingReactions))
 
                    {
 
                        statusLabel.Text = "WARNING: This recipe includes reactions that have not yet been recorded.";
 
                    }
 
                    foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients)
 
                    {
 
                        Gtk.Label label = new Gtk.Label(ingredient.quantity.ToString() + " " + ingredient.name);
 
                        recipeListBox.PackStart(label);
 
                        label.Show();
 
                        store.AppendValues(ingredient.quantity.ToString(), ingredient.name);
 
                    }
 
                }
 
                paintSwatch.Color = recipe.ReactedColor;
 
            }
 
        }
 

	
 
        protected void OnExportToWiki(object sender, EventArgs e)
 
        {
 
            Gtk.FileChooserDialog fileDialog =
 
                new Gtk.FileChooserDialog("Select destination file.",
 
                        this, Gtk.FileChooserAction.Save,
 
                        Gtk.Stock.Cancel, Gtk.ResponseType.Cancel,
 
                        Gtk.Stock.Save, Gtk.ResponseType.Accept);
 
            Gtk.ResponseType resp = (Gtk.ResponseType)fileDialog.Run();
 
            if (resp == Gtk.ResponseType.Accept)
 
            {
 
                string fileName = fileDialog.Filename;
 
                string directory = fileDialog.CurrentFolder;
 
                profile.ExportWikiRecipes(System.IO.Path.Combine(directory, fileName));
 
            }
 
            fileDialog.Destroy();
 
        }
 

	
 
        protected void OnShowIngredients(object sender, EventArgs e)
 
        {
 
            ReagentWindow win = new ReagentWindow(profile);
 
            win.Show();
 
        }
 

	
 
        protected void OnDestroyed(object o, EventArgs args)
 
        {
 
            if (running)
 
            {
 
                // window closed while generator running: stop and save
 
                generator.Finished -= OnFinished;
 
                generator.Progress -= OnProgress;
 
                generator.NewRecipe -= OnNewRecipe;
 
                generator.Stop();
 
                generator.Wait();
 
                generator.SaveState(System.IO.Path.Combine(profile.Directory, STATE_FILE));
 
            }
 
        }
 

	
 
        // Reagent view handling
 
        private void RenderReagentToggle (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
 
        {
 
            Reagent reagent = (Reagent) model.GetValue (iter, 0);
 
            Gtk.CellRendererToggle toggle = (cell as Gtk.CellRendererToggle);
 
            toggle.Active = reagent.Enabled;
 
            toggle.Activatable = !reagent.IsCatalyst;
 
        }
 
        private void RenderReagentCost (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
 
        {
 
            Reagent reagent = (Reagent) model.GetValue (iter, 0);
 
            (cell as Gtk.CellRendererText).Text = reagent.Cost.ToString();
 
        }
 
        private void RenderReagentQuantity (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
 
        {
 
            Reagent reagent = (Reagent) model.GetValue (iter, 0);
 
            (cell as Gtk.CellRendererText).Text = reagent.RecipeMax.ToString();
 
        }
 

	
 
        private void OnReagentCostChanged(object o, Gtk.EditedArgs args)
 
        {
 
            uint newCost;
 
            if (uint.TryParse(args.NewText, out newCost))
 
            {
 
                Gtk.TreeIter iter;
 
                reagentListStore.GetIter (out iter, new Gtk.TreePath (args.Path));
 
             
 
                Reagent reagent = (Reagent) reagentListStore.GetValue (iter, 0);
 
                if (reagent.Cost != newCost)
 
                {
 
                    reagent.Cost = newCost;
 
                    SaveReagentSettings();
 
                }
 
            }
 
        }
 

	
 
        private void OnReagentQuantityChanged(object o, Gtk.EditedArgs args)
 
        {
 
            uint newMax;
 
            if (uint.TryParse(args.NewText, out newMax))
 
            {
 
                Gtk.TreeIter iter;
 
                reagentListStore.GetIter (out iter, new Gtk.TreePath (args.Path));
 
             
 
                Reagent reagent = (Reagent) reagentListStore.GetValue (iter, 0);
 
                if (reagent.RecipeMax != newMax)
 
                {
 
                    reagent.RecipeMax = newMax;
 
                    SaveReagentSettings();
 
                }
 
            }
 
        }
 

	
 
        private void OnReagentEnableToggled(object o, Gtk.ToggledArgs args)
 
        {
 
            Gtk.CellRendererToggle btn = (Gtk.CellRendererToggle)o;
 
            Gtk.TreeIter iter;
 
            reagentListStore.GetIter (out iter, new Gtk.TreePath (args.Path));
 
         
 
            Reagent reagent = (Reagent) reagentListStore.GetValue (iter, 0);
 
            if (btn.Active != reagent.Enabled)
 
            {
 
                reagent.Enabled = btn.Active;
 
                SaveReagentSettings();
 
            }
 
        }
 

	
 
        private void SaveReagentSettings()
 
        {
 
            // save out state
 
            ReagentManager.SaveProfileReagents(profile.ReagentFile);
 
        }
 

	
 
        protected void OnCopyRecipeToClipboard(object sender, EventArgs e)
 
        {
 
            Gtk.TreeModel model;
 
            Gtk.TreeIter iter;
 
            Gtk.Clipboard clipboard = recipeIngredientsView.GetClipboard(Gdk.Selection.Clipboard);
 
            
 
            Gtk.TreeSelection selection = recipeList.Selection;
 
            if ((selection != null) && selection.GetSelected(out model, out iter))
 
            {
 
                string colorName = (string)colorStore.GetValue(iter, 0);
 
                PaintRecipe recipe;
 
                if (profile.Recipes.TryGetValue(colorName, out recipe))
 
                {
 
                    clipboard.Text = recipe.ToString();
 
                }
 
            }
 
        }
 

	
 
        protected void OnCopyRecipeListToClipboard(object sender, EventArgs e)
 
        {
 
            Gtk.Clipboard clipboard = recipeList.GetClipboard(Gdk.Selection.Clipboard);
 

	
 
            StringWriter writer = new StringWriter();
 
            profile.ExportWikiFormat(writer);
 

	
 
            clipboard.Text = writer.ToString();
 
        }
 
    }
 
}
 

	
RecipeSearchNode.cs
Show inline comments
 
new file 100644
 
/*
 
 * Copyright (c) 2015, Jason Maltzen
 

	
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 
 of this software and associated documentation files (the "Software"), to deal
 
 in the Software without restriction, including without limitation the rights
 
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
 copies of the Software, and to permit persons to whom the Software is
 
 furnished to do so, subject to the following conditions:
 

	
 
 The above copyright notice and this permission notice shall be included in
 
 all copies or substantial portions of the Software.
 

	
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
 THE SOFTWARE.
 
*/
 

	
 
using System;
 
using System.Collections.Generic;
 
using System.IO;
 
using System.Text.RegularExpressions;
 

	
 
namespace DesertPaintLab
 
{
 
    public class RecipeSearchNode
 
    {
 
        //int initialReagentCount;
 
        uint[] reagents;
 
        public uint[] Reagents
 
        {
 
            get
 
            {
 
                return reagents;
 
            }
 
        }
 
        uint INVALID_REAGENT;
 
        int nextReagentPos;
 
        public int ReagentCount
 
        {
 
            get
 
            {
 
                return nextReagentPos;
 
            }
 
        }
 

	
 
        bool[] reagentInUse;
 
        List<Reagent> costSortedReagents;
 
        PaintRecipe testRecipe = null;
 
        public PaintRecipe TestRecipe
 
        {
 
            get
 
            {
 
                return testRecipe;
 
            }
 
            set
 
            {
 
                testRecipe = value;
 
            }
 
        }
 

	
 
        public int InitialCount { get; private set; }
 
        public uint CurrentTargetQuantity { get; set; }
 
        public uint MaxQuantity { get; set; }
 
        public uint UsedQuantity { get; private set; }
 
        public uint CatalystCount { get; set; }
 
        public uint FullQuantityDepth { get; set; }
 
        public uint FullQuantity { get; set; }
 

	
 
        uint maxReagents;
 
        public uint MaxReagents
 
        {
 
            get
 
            {
 
                return maxReagents;
 
            }
 
            set
 
            {
 
                maxReagents = value;
 
                currentWeights = new uint[maxReagents];
 
            }
 
        }
 

	
 
        uint[] currentWeights;
 
        public uint[] CurrentWeights
 
        {
 
            get
 
            {
 
                return currentWeights;
 
            }
 
        }
 

	
 
        public RecipeSearchNode(List<Reagent> costSortedReagents, uint[] reagents)
 
        {
 
            this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            nextReagentPos = reagents.Length;
 
            for (int i = this.reagents.Length-1; i >= this.reagents.Length; --i)
 
            {
 
                reagents[i] = INVALID_REAGENT;
 
            }
 
            for (int i = reagents.Length-1; i >= 0; --i)
 
            {
 
                this.reagents[i] = reagents[i];
 
                if (reagents[i] == INVALID_REAGENT)
 
                {
 
                    nextReagentPos = i;
 
                }
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
            foreach (uint reagentIdx in this.reagents)
 
            {
 
                if (reagentIdx != INVALID_REAGENT)
 
                {
 
                    reagentInUse[reagentIdx] = true;
 
                }
 
            }
 
            InitialCount = nextReagentPos;
 
            MaxReagents = (uint)nextReagentPos; // better set this later!
 
            UsedQuantity = 0;
 
        }
 

	
 
        // top-level search
 
        public RecipeSearchNode(List<Reagent> costSortedReagents, uint startReagent)
 
        {
 
            this.costSortedReagents = new List<Reagent>(costSortedReagents);
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            nextReagentPos = 0;
 
            for (int i = 0; i < reagents.Length; ++i)
 
            {
 
                this.reagents[i] = INVALID_REAGENT;
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
            this.reagents[nextReagentPos++] = NextFreeReagent(startReagent);
 
            //Console.WriteLine("Added reagent {0} at pos {1}", this.reagents[nextReagentPos-1], nextReagentPos-1);
 
            InitialCount = 1; // don't iterate up beyond the start reagent
 
            MaxReagents = 1;
 
            UsedQuantity = 0;
 
        }
 

	
 
        public RecipeSearchNode(List<Reagent> costSortedReagents)
 
        {
 
            this.costSortedReagents = costSortedReagents;
 
            this.reagents = new uint[costSortedReagents.Count];
 
            INVALID_REAGENT = (uint)costSortedReagents.Count;
 
            nextReagentPos = 0;
 
            for (int i = 0; i < reagents.Length; ++i)
 
            {
 
                this.reagents[i] = INVALID_REAGENT;
 
            }
 
            reagentInUse = new bool[costSortedReagents.Count];
 
            for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx)
 
            {
 
                reagentInUse[reagentIdx] = false;
 
            }
 
            this.reagents[nextReagentPos++] = NextFreeReagent(0);
 
            InitialCount = 0;
 
            MaxReagents = 1;
 
            UsedQuantity = 0;
 
        }
 

	
 
        public Reagent Reagent(int idx)
 
        {
 
            return costSortedReagents[(int)reagents[idx]];
 
        }
 

	
 
        public uint LastReagent
 
        {
 
            get
 
            {
 
                return reagents[nextReagentPos - 1];
 
            }
 
        }
 

	
 
        public void RemoveLastReagent()
 
        {
 
            uint reagentIdx = reagents[nextReagentPos-1];
 
            ReleaseReagent(reagentIdx);
 
            if (costSortedReagents[(int)reagentIdx].IsCatalyst)
 
            {
 
                --CatalystCount;
 
            }
 
            reagents[nextReagentPos-1] = INVALID_REAGENT;
 
            --nextReagentPos;
 
        }
 

	
 
        public void ReplaceLastReagent(uint reagentIdx)
 
        {
 
            uint oldReagentIdx = reagents[nextReagentPos-1];
 
            ReleaseReagent(oldReagentIdx);
 
            reagents[nextReagentPos-1] = reagentIdx;
 
            if (costSortedReagents[(int)oldReagentIdx].IsCatalyst)
 
            {
 
                --CatalystCount;
 
            }
 
            if (costSortedReagents[(int)reagentIdx].IsCatalyst)
 
            {
 
                ++CatalystCount;
 
            }
 
        }
 

	
 
        public uint NextFreeReagent(uint startIdx)
 
        {
 
            uint idx = startIdx;
 
            for (; idx < costSortedReagents.Count; ++idx)
 
            {
 
                bool inUse = reagentInUse[idx];
 
                if ((inUse == false) && (costSortedReagents[(int)idx].Enabled))
 
                {
 
                    //Console.WriteLine("Found free reagent idx {0}", idx);
 
                    reagentInUse[idx] = true;
 
                    return idx;
 
                }
 
            }
 
            //Console.WriteLine("Failed to find free reagent.");
 
            return (uint)costSortedReagents.Count;
 
        }
 

	
 
        private void ReleaseReagent(uint reagentIdx)
 
        {
 
            reagentInUse[reagentIdx] = false;
 
        }
 

	
 
        public bool AddNextReagent()
 
        {
 
            bool ok = (nextReagentPos < MaxReagents);
 
            if (ok)
 
            {
 
                uint nextReagent = NextFreeReagent(0);
 
                reagents[nextReagentPos++] = nextReagent;
 
                if (costSortedReagents[(int)nextReagent].IsCatalyst)
 
                {
 
                    ++CatalystCount;
 
                }
 
                InitForQuantity(CurrentTargetQuantity);
 
            }
 
            return ok;
 
        }
 

	
 
        public void InitForQuantity(uint quantity)
 
        {
 
            //System.Console.WriteLine("Init for quantity: {0}, reagent count: {1} ({2} catalysts)", quantity, ReagentCount, CatalystCount);
 
            CurrentTargetQuantity = quantity;
 
            if (CurrentTargetQuantity < (10 + CatalystCount))
 
            {
 
                // invalid quantity
 
                return;
 
            }
 
            UsedQuantity = 0;
 
            uint remainingReagents = ((uint)nextReagentPos - CatalystCount);
 
            uint remainingWeight = CurrentTargetQuantity - CatalystCount;
 
            for (int i = 0; i < nextReagentPos; ++i)
 
            {
 
                Reagent reagent = Reagent(i);
 

	
 
                if (reagent.IsCatalyst)
 
                {
 
                    //Console.WriteLine("Init catalyst {0} weight 1", reagent.Name);
 
                    currentWeights[i] = 1;
 
                    ++UsedQuantity;
 
                }
 
                else
 
                {
 
                    uint reagentMaxWeight = reagent.RecipeMax;
 
                    if (ReagentCount <= FullQuantityDepth)
 
                    {
 
                        reagentMaxWeight = Math.Max(FullQuantity, reagentMaxWeight);
 
                    }
 
                    uint weight = (uint)Math.Min(remainingWeight - (remainingReagents-1), reagentMaxWeight);
 
                    //Console.WriteLine("Init reagent {0} weight {1}", reagent.Name, weight);
 
                    remainingWeight -= weight;
 
                    currentWeights[i] = weight;
 
                    UsedQuantity += weight;
 
                }
 
                --remainingReagents;
 
            }
 
        }
 

	
 
        public void SetWeight(int idx, uint quantity)
 
        {
 
            UsedQuantity -= currentWeights[idx];
 
            currentWeights[idx] = quantity;
 
            UsedQuantity += quantity;
 
        }
 

	
 
        public void SaveState(StreamWriter writer)
 
        {
 
            writer.WriteLine("---SearchNode---");
 
            writer.WriteLine("MaxReagents: {0}", MaxReagents);
 
            writer.WriteLine("Reagents: {0}", nextReagentPos);
 
            for (int i = 0; i < nextReagentPos; ++i)
 
            {
 
                uint idx = reagents[i];
 
                uint weight = currentWeights[i];
 
                writer.WriteLine("Reagent: {0},{1},{2}", idx, reagentInUse[idx] ? 1 : 0, weight);
 
            }
 
            // pulled from parent: List<Reagent> costSortedReagents;
 
            // new on construct: PaintRecipe testRecipe = null;
 
            writer.WriteLine("CurrentTargetQuantity: {0}", CurrentTargetQuantity);
 
            writer.WriteLine("MaxQuantity: {0}", MaxQuantity);
 
            writer.WriteLine("UsedQuantity: {0}", UsedQuantity);
 
            writer.WriteLine("CatalystCount: {0}", CatalystCount);
 
            writer.WriteLine("InitialCount: {0}", InitialCount);
 
            writer.WriteLine("FullQuantity: {0}", FullQuantity);
 
            writer.WriteLine("FullQuantityDepth: {0}", FullQuantityDepth);
 
            writer.WriteLine("---EndNode---");
 
            
 
        }
 

	
 
        static Regex keyValueRegex = new Regex(@"(\w+)\:\s*(.*)\s*$");
 
        static Regex reagentPartsRegex = new Regex(@"(?<id>\d+),(?<inUse>\d+),(?<weight>\d+)");
 
        public bool LoadState(StreamReader reader)
 
        {
 
            string line = reader.ReadLine();
 
            if (!line.Equals("---SearchNode---"))
 
            {
 
                return false;
 
            }
 

	
 
            bool success = true;
 
            Match match;
 
            while ((line = reader.ReadLine()) != null)
 
            {
 
                if (line.Equals("---EndNode---"))
 
                {
 
                    break;
 
                }
 
                match = keyValueRegex.Match(line);
 
                if (match.Success)
 
                {
 
                    switch (match.Groups[1].Value)
 
                    {
 
                        case "Reagents":
 
                            {
 
                                //int reagentCount = int.Parse(match.Groups[2].Value);
 
                                for (int i = 0; i < reagents.Length; ++i)
 
                                {
 
                                    reagents[i] = INVALID_REAGENT;
 
                                    reagentInUse[i] = false;
 
                                }
 
                                nextReagentPos = 0;
 
                            }
 
                            break;
 
                        case "Reagent":
 
                            {
 
                                Match reagentInfo = reagentPartsRegex.Match(match.Groups[2].Value);
 
                                if (reagentInfo.Success)
 
                                {
 
                                    uint reagentId = uint.Parse(reagentInfo.Groups["id"].Value);
 
                                    int isInUse = int.Parse(reagentInfo.Groups["inUse"].Value);
 
                                    uint weight = uint.Parse(reagentInfo.Groups["weight"].Value);
 
                                    reagents[nextReagentPos] = reagentId;
 
                                    currentWeights[nextReagentPos] = weight;
 
                                    if (isInUse != 0)
 
                                    {
 
                                        if (reagentId != INVALID_REAGENT)
 
                                        {
 
                                            reagentInUse[reagentId] = true;
 
                                        }
 
                                    }
 
                                    ++nextReagentPos;
 
                                }
 
                                else
 
                                {
 
                                    success = false;
 
                                }
 
                            }
 
                            break;
 
                        case "CurrentTargetQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                CurrentTargetQuantity = value;
 
                            }
 
                            break;
 
                        case "MaxQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                MaxQuantity = value;
 
                            }
 
                            break;
 
                        case "MaxReagents":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                MaxReagents = value;
 
                            }
 
                            break;
 
                        case "UsedQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                UsedQuantity = value;
 
                            }
 
                            break;
 
                        case "CatalystCount":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                CatalystCount = value;
 
                            }
 
                            break;
 
                        case "InitialCount":
 
                            {
 
                                int value = int.Parse(match.Groups[2].Value);
 
                                InitialCount = value;
 
                            }
 
                            break;
 
                        case "FullQuantity":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                FullQuantity = value;
 
                            }
 
                            break;
 
                        case "FullQuantityDepth":
 
                            {
 
                                uint value = uint.Parse(match.Groups[2].Value);
 
                                FullQuantityDepth = value;
 
                            }
 
                            break;
 
                        default:
 
                            success = false;
 
                            break;
 
                    }
 
                }
 
                else
 
                {
 
                    success = false;
 
                    break;
 
                }
 
            }
 
            return success;
 
        }
 
    }
 
}
 

	
bin/Debug/DesertPaintLab/colors.txt
Show inline comments
 
deleted file
bin/Debug/DesertPaintLab/template/dp_reactions.txt
Show inline comments
 
deleted file
bin/Debug/DesertPaintLab/template/ingredients.txt
Show inline comments
 
deleted file
gtk-gui/DesertPaintLab.RecipeGeneratorWindow.cs
Show inline comments
 

	
 
// This file has been generated by the GUI designer. Do not modify.
 
namespace DesertPaintLab
 
{
 
	public partial class RecipeGeneratorWindow
 
	{
 
		private global::Gtk.UIManager UIManager;
 
		
 
		private global::Gtk.Action ExportAction;
 
		
 
		private global::Gtk.Action ExportToWikiAction;
 
		
 
		private global::Gtk.Action SettingsAction;
 
		
 
		private global::Gtk.Action IngredientsAction;
 
		
 
		private global::Gtk.Action CopyToClipboardAction;
 
		
 
		private global::Gtk.VBox vbox2;
 
		
 
		private global::Gtk.MenuBar menubar1;
 
		
 
		private global::Gtk.HBox hbox1;
 
		
 
		private global::Gtk.VBox vbox8;
 
		
 
		private global::Gtk.Label label3;
 
		private global::Gtk.HBox hbox2;
 
		
 
		private global::Gtk.SpinButton maxIngredientsSpinButton;
 
		
 
		private global::Gtk.Label label3;
 
		
 
		private global::Gtk.HSeparator hseparator3;
 
		
 
		private global::Gtk.Label label4;
 
		private global::Gtk.HBox hbox4;
 
		
 
		private global::Gtk.SpinButton maxRecipeSpinButton;
 
		
 
		private global::Gtk.Label label4;
 
		
 
		private global::Gtk.HSeparator hseparator4;
 
		
 
		private global::Gtk.HBox hbox5;
 
		
 
		private global::Gtk.SpinButton fullQuantityDepthSpinButton;
 
		
 
		private global::Gtk.Label label8;
 
		
 
		private global::Gtk.SpinButton fullQuantityDepthSpinButton;
 
		private global::Gtk.HSeparator hseparator5;
 
		
 
		private global::Gtk.HBox hbox6;
 
		
 
		private global::Gtk.SpinButton fullQuantitySpinButton;
 
		
 
		private global::Gtk.Label label9;
 
		
 
		private global::Gtk.SpinButton fullQuantitySpinButton;
 
		private global::Gtk.Frame frame3;
 
		
 
		private global::Gtk.Alignment GtkAlignment1;
 
		
 
		private global::Gtk.ScrolledWindow scrolledwindow2;
 
		
 
		private global::Gtk.TreeView reagentListView;
 
		
 
		private global::Gtk.Label GtkLabel4;
 
		
 
		private global::Gtk.HSeparator hseparator6;
 
		
 
		private global::Gtk.ScrolledWindow GtkScrolledWindow1;
 
		
 
		private global::Gtk.TreeView recipeList;
 
		
 
		private global::Gtk.VBox vbox3;
 
		
 
		private global::Gtk.Frame frame2;
 
		
 
		private global::Gtk.Alignment GtkAlignment;
 
		private global::Gtk.ScrolledWindow GtkScrolledWindow;
 
		
 
		private global::Gtk.ScrolledWindow scrolledwindow1;
 
		
 
		private global::Gtk.VBox recipeListBox;
 
		private global::Gtk.TreeView recipeIngredientsView;
 
		
 
		private global::Gtk.Label recipeLabel;
 
		
 
		private global::Gtk.Button button919;
 
		
 
		private global::DesertPaintLab.PaintSwatch paintSwatch;
 
		
 
		private global::Gtk.HBox hbox3;
 
		
 
		private global::Gtk.HSeparator hseparator2;
 
		
 
		private global::Gtk.Button stopResumeButton;
 
		
 
		private global::Gtk.Label countLabel;
 
		
 
		private global::Gtk.Button beginButton;
 
		
 
		private global::Gtk.HSeparator hseparator1;
 
		
 
		private global::Gtk.Label statusLabel;
 
		
 
		private global::Gtk.ProgressBar progressBar;
 

	
 
		protected virtual void Build ()
 
		{
 
			global::Stetic.Gui.Initialize (this);
 
			// Widget DesertPaintLab.RecipeGeneratorWindow
 
			this.UIManager = new global::Gtk.UIManager ();
 
			global::Gtk.ActionGroup w1 = new global::Gtk.ActionGroup ("Default");
 
			this.ExportAction = new global::Gtk.Action ("ExportAction", "Export", null, null);
 
			this.ExportAction.ShortLabel = "Export";
 
			w1.Add (this.ExportAction, null);
 
			this.ExportToWikiAction = new global::Gtk.Action ("ExportToWikiAction", "Export to Wiki", null, null);
 
			this.ExportToWikiAction.ShortLabel = "Export to Wiki";
 
			w1.Add (this.ExportToWikiAction, null);
 
			this.SettingsAction = new global::Gtk.Action ("SettingsAction", "Settings", null, null);
 
			this.SettingsAction.ShortLabel = "Tools";
 
			w1.Add (this.SettingsAction, null);
 
			this.IngredientsAction = new global::Gtk.Action ("IngredientsAction", "Ingredients", null, null);
 
			this.IngredientsAction.ShortLabel = "Ingredients";
 
			w1.Add (this.IngredientsAction, null);
 
			this.CopyToClipboardAction = new global::Gtk.Action ("CopyToClipboardAction", "Copy to Clipboard", null, null);
 
			this.CopyToClipboardAction.ShortLabel = "Copy to Clipboard";
 
			w1.Add (this.CopyToClipboardAction, null);
 
			this.UIManager.InsertActionGroup (w1, 0);
 
			this.AddAccelGroup (this.UIManager.AccelGroup);
 
			this.Name = "DesertPaintLab.RecipeGeneratorWindow";
 
			this.Title = "Recipe Generator";
 
			this.WindowPosition = ((global::Gtk.WindowPosition)(4));
 
			// Container child DesertPaintLab.RecipeGeneratorWindow.Gtk.Container+ContainerChild
 
			this.vbox2 = new global::Gtk.VBox ();
 
			this.vbox2.Name = "vbox2";
 
			this.vbox2.Spacing = 6;
 
			this.vbox2.BorderWidth = ((uint)(8));
 
			// Container child vbox2.Gtk.Box+BoxChild
 
			this.UIManager.AddUiFromString ("<ui><menubar name='menubar1'><menu name='ExportAction' action='ExportAction'><menuitem name='ExportToWikiAction' action='ExportToWikiAction'/></menu><menu name='SettingsAction' action='SettingsAction'><menuitem name='IngredientsAction' action='IngredientsAction'/></menu></menubar></ui>");
 
			this.UIManager.AddUiFromString ("<ui><menubar name='menubar1'><menu name='ExportAction' action='ExportAction'><menuitem name='ExportToWikiAction' action='ExportToWikiAction'/><menuitem name='CopyToClipboardAction' action='CopyToClipboardAction'/></menu></menubar></ui>");
 
			this.menubar1 = ((global::Gtk.MenuBar)(this.UIManager.GetWidget ("/menubar1")));
 
			this.menubar1.Name = "menubar1";
 
			this.vbox2.Add (this.menubar1);
 
			global::Gtk.Box.BoxChild w2 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.menubar1]));
 
			w2.Position = 0;
 
			w2.Expand = false;
 
			w2.Fill = false;
 
			// Container child vbox2.Gtk.Box+BoxChild
 
			this.hbox1 = new global::Gtk.HBox ();
 
			this.hbox1.Name = "hbox1";
 
			this.hbox1.Spacing = 6;
 
			// Container child hbox1.Gtk.Box+BoxChild
 
			this.vbox8 = new global::Gtk.VBox ();
 
			this.vbox8.Name = "vbox8";
 
			this.vbox8.Spacing = 6;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.label3 = new global::Gtk.Label ();
 
			this.label3.Name = "label3";
 
			this.label3.LabelProp = "Maximum Ingredients";
 
			this.vbox8.Add (this.label3);
 
			global::Gtk.Box.BoxChild w3 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label3]));
 
			w3.Position = 0;
 
			w3.Expand = false;
 
			w3.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hbox2 = new global::Gtk.HBox ();
 
			this.hbox2.Name = "hbox2";
 
			this.hbox2.Spacing = 6;
 
			// Container child hbox2.Gtk.Box+BoxChild
 
			this.maxIngredientsSpinButton = new global::Gtk.SpinButton (0, 14, 1);
 
			this.maxIngredientsSpinButton.CanFocus = true;
 
			this.maxIngredientsSpinButton.Name = "maxIngredientsSpinButton";
 
			this.maxIngredientsSpinButton.Adjustment.PageIncrement = 10;
 
			this.maxIngredientsSpinButton.ClimbRate = 1;
 
			this.maxIngredientsSpinButton.Numeric = true;
 
			this.vbox8.Add (this.maxIngredientsSpinButton);
 
			global::Gtk.Box.BoxChild w4 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.maxIngredientsSpinButton]));
 
			this.hbox2.Add (this.maxIngredientsSpinButton);
 
			global::Gtk.Box.BoxChild w3 = ((global::Gtk.Box.BoxChild)(this.hbox2 [this.maxIngredientsSpinButton]));
 
			w3.Position = 0;
 
			w3.Expand = false;
 
			w3.Fill = false;
 
			// Container child hbox2.Gtk.Box+BoxChild
 
			this.label3 = new global::Gtk.Label ();
 
			this.label3.Name = "label3";
 
			this.label3.LabelProp = "Maximum Ingredients";
 
			this.hbox2.Add (this.label3);
 
			global::Gtk.Box.BoxChild w4 = ((global::Gtk.Box.BoxChild)(this.hbox2 [this.label3]));
 
			w4.Position = 1;
 
			w4.Expand = false;
 
			w4.Fill = false;
 
			this.vbox8.Add (this.hbox2);
 
			global::Gtk.Box.BoxChild w5 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hbox2]));
 
			w5.Position = 0;
 
			w5.Expand = false;
 
			w5.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hseparator3 = new global::Gtk.HSeparator ();
 
			this.hseparator3.Name = "hseparator3";
 
			this.vbox8.Add (this.hseparator3);
 
			global::Gtk.Box.BoxChild w5 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator3]));
 
			w5.Position = 2;
 
			w5.Expand = false;
 
			w5.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.label4 = new global::Gtk.Label ();
 
			this.label4.Name = "label4";
 
			this.label4.LabelProp = "Max Total Quantity";
 
			this.label4.UseMarkup = true;
 
			this.label4.Wrap = true;
 
			this.vbox8.Add (this.label4);
 
			global::Gtk.Box.BoxChild w6 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label4]));
 
			w6.Position = 3;
 
			global::Gtk.Box.BoxChild w6 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator3]));
 
			w6.Position = 1;
 
			w6.Expand = false;
 
			w6.Fill = false;
 
			w6.Padding = ((uint)(8));
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hbox4 = new global::Gtk.HBox ();
 
			this.hbox4.Name = "hbox4";
 
			this.hbox4.Spacing = 6;
 
			// Container child hbox4.Gtk.Box+BoxChild
 
			this.maxRecipeSpinButton = new global::Gtk.SpinButton (0, 100, 1);
 
			this.maxRecipeSpinButton.CanFocus = true;
 
			this.maxRecipeSpinButton.Name = "maxRecipeSpinButton";
 
			this.maxRecipeSpinButton.Adjustment.PageIncrement = 10;
 
			this.maxRecipeSpinButton.ClimbRate = 1;
 
			this.maxRecipeSpinButton.Numeric = true;
 
			this.vbox8.Add (this.maxRecipeSpinButton);
 
			global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.maxRecipeSpinButton]));
 
			w7.Position = 4;
 
			this.hbox4.Add (this.maxRecipeSpinButton);
 
			global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.hbox4 [this.maxRecipeSpinButton]));
 
			w7.Position = 0;
 
			w7.Expand = false;
 
			w7.Fill = false;
 
			// Container child hbox4.Gtk.Box+BoxChild
 
			this.label4 = new global::Gtk.Label ();
 
			this.label4.Name = "label4";
 
			this.label4.LabelProp = "Max Total Quantity";
 
			this.label4.UseMarkup = true;
 
			this.label4.Wrap = true;
 
			this.hbox4.Add (this.label4);
 
			global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.hbox4 [this.label4]));
 
			w8.Position = 1;
 
			w8.Expand = false;
 
			w8.Fill = false;
 
			this.vbox8.Add (this.hbox4);
 
			global::Gtk.Box.BoxChild w9 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hbox4]));
 
			w9.Position = 2;
 
			w9.Expand = false;
 
			w9.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hseparator4 = new global::Gtk.HSeparator ();
 
			this.hseparator4.Name = "hseparator4";
 
			this.vbox8.Add (this.hseparator4);
 
			global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator4]));
 
			w8.Position = 5;
 
			w8.Expand = false;
 
			w8.Fill = false;
 
			global::Gtk.Box.BoxChild w10 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator4]));
 
			w10.Position = 3;
 
			w10.Expand = false;
 
			w10.Fill = false;
 
			w10.Padding = ((uint)(8));
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.label8 = new global::Gtk.Label ();
 
			this.label8.Name = "label8";
 
			this.label8.LabelProp = "Full Quantity Depth";
 
			this.vbox8.Add (this.label8);
 
			global::Gtk.Box.BoxChild w9 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label8]));
 
			w9.Position = 6;
 
			w9.Expand = false;
 
			w9.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hbox5 = new global::Gtk.HBox ();
 
			this.hbox5.Name = "hbox5";
 
			this.hbox5.Spacing = 6;
 
			// Container child hbox5.Gtk.Box+BoxChild
 
			this.fullQuantityDepthSpinButton = new global::Gtk.SpinButton (0, 15, 1);
 
			this.fullQuantityDepthSpinButton.CanFocus = true;
 
			this.fullQuantityDepthSpinButton.Name = "fullQuantityDepthSpinButton";
 
			this.fullQuantityDepthSpinButton.Adjustment.PageIncrement = 10;
 
			this.fullQuantityDepthSpinButton.ClimbRate = 1;
 
			this.fullQuantityDepthSpinButton.Numeric = true;
 
			this.vbox8.Add (this.fullQuantityDepthSpinButton);
 
			global::Gtk.Box.BoxChild w10 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.fullQuantityDepthSpinButton]));
 
			w10.Position = 7;
 
			w10.Expand = false;
 
			w10.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.label9 = new global::Gtk.Label ();
 
			this.label9.Name = "label9";
 
			this.label9.LabelProp = "FullQuantity";
 
			this.vbox8.Add (this.label9);
 
			global::Gtk.Box.BoxChild w11 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label9]));
 
			w11.Position = 8;
 
			this.hbox5.Add (this.fullQuantityDepthSpinButton);
 
			global::Gtk.Box.BoxChild w11 = ((global::Gtk.Box.BoxChild)(this.hbox5 [this.fullQuantityDepthSpinButton]));
 
			w11.Position = 0;
 
			w11.Expand = false;
 
			w11.Fill = false;
 
			// Container child hbox5.Gtk.Box+BoxChild
 
			this.label8 = new global::Gtk.Label ();
 
			this.label8.Name = "label8";
 
			this.label8.LabelProp = "Full Quantity Depth";
 
			this.hbox5.Add (this.label8);
 
			global::Gtk.Box.BoxChild w12 = ((global::Gtk.Box.BoxChild)(this.hbox5 [this.label8]));
 
			w12.Position = 1;
 
			w12.Expand = false;
 
			w12.Fill = false;
 
			this.vbox8.Add (this.hbox5);
 
			global::Gtk.Box.BoxChild w13 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hbox5]));
 
			w13.Position = 4;
 
			w13.Expand = false;
 
			w13.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hseparator5 = new global::Gtk.HSeparator ();
 
			this.hseparator5.Name = "hseparator5";
 
			this.vbox8.Add (this.hseparator5);
 
			global::Gtk.Box.BoxChild w14 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator5]));
 
			w14.Position = 5;
 
			w14.Expand = false;
 
			w14.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hbox6 = new global::Gtk.HBox ();
 
			this.hbox6.Name = "hbox6";
 
			this.hbox6.Spacing = 6;
 
			// Container child hbox6.Gtk.Box+BoxChild
 
			this.fullQuantitySpinButton = new global::Gtk.SpinButton (0, 30, 1);
 
			this.fullQuantitySpinButton.CanFocus = true;
 
			this.fullQuantitySpinButton.Name = "fullQuantitySpinButton";
 
			this.fullQuantitySpinButton.Adjustment.PageIncrement = 10;
 
			this.fullQuantitySpinButton.ClimbRate = 1;
 
			this.fullQuantitySpinButton.Numeric = true;
 
			this.vbox8.Add (this.fullQuantitySpinButton);
 
			global::Gtk.Box.BoxChild w12 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.fullQuantitySpinButton]));
 
			w12.Position = 9;
 
			w12.Expand = false;
 
			w12.Fill = false;
 
			this.hbox6.Add (this.fullQuantitySpinButton);
 
			global::Gtk.Box.BoxChild w15 = ((global::Gtk.Box.BoxChild)(this.hbox6 [this.fullQuantitySpinButton]));
 
			w15.Position = 0;
 
			w15.Expand = false;
 
			w15.Fill = false;
 
			// Container child hbox6.Gtk.Box+BoxChild
 
			this.label9 = new global::Gtk.Label ();
 
			this.label9.Name = "label9";
 
			this.label9.LabelProp = "FullQuantity";
 
			this.hbox6.Add (this.label9);
 
			global::Gtk.Box.BoxChild w16 = ((global::Gtk.Box.BoxChild)(this.hbox6 [this.label9]));
 
			w16.Position = 1;
 
			w16.Expand = false;
 
			w16.Fill = false;
 
			this.vbox8.Add (this.hbox6);
 
			global::Gtk.Box.BoxChild w17 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hbox6]));
 
			w17.Position = 6;
 
			w17.Expand = false;
 
			w17.Fill = false;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.frame3 = new global::Gtk.Frame ();
 
			this.frame3.Name = "frame3";
 
			this.frame3.ShadowType = ((global::Gtk.ShadowType)(0));
 
			// Container child frame3.Gtk.Container+ContainerChild
 
			this.GtkAlignment1 = new global::Gtk.Alignment (0F, 0F, 1F, 1F);
 
			this.GtkAlignment1.Name = "GtkAlignment1";
 
			this.GtkAlignment1.LeftPadding = ((uint)(12));
 
			// Container child GtkAlignment1.Gtk.Container+ContainerChild
 
			this.scrolledwindow2 = new global::Gtk.ScrolledWindow ();
 
			this.scrolledwindow2.CanFocus = true;
 
			this.scrolledwindow2.Name = "scrolledwindow2";
 
			this.scrolledwindow2.ShadowType = ((global::Gtk.ShadowType)(1));
 
			// Container child scrolledwindow2.Gtk.Container+ContainerChild
 
			this.reagentListView = new global::Gtk.TreeView ();
 
			this.reagentListView.WidthRequest = 300;
 
			this.reagentListView.CanFocus = true;
 
			this.reagentListView.Name = "reagentListView";
 
			this.scrolledwindow2.Add (this.reagentListView);
 
			this.GtkAlignment1.Add (this.scrolledwindow2);
 
			this.frame3.Add (this.GtkAlignment1);
 
			this.GtkLabel4 = new global::Gtk.Label ();
 
			this.GtkLabel4.Name = "GtkLabel4";
 
			this.GtkLabel4.LabelProp = "<b>Ingredients</b>";
 
			this.GtkLabel4.UseMarkup = true;
 
			this.frame3.LabelWidget = this.GtkLabel4;
 
			this.vbox8.Add (this.frame3);
 
			global::Gtk.Box.BoxChild w21 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.frame3]));
 
			w21.PackType = ((global::Gtk.PackType)(1));
 
			w21.Position = 7;
 
			// Container child vbox8.Gtk.Box+BoxChild
 
			this.hseparator6 = new global::Gtk.HSeparator ();
 
			this.hseparator6.Name = "hseparator6";
 
			this.vbox8.Add (this.hseparator6);
 
			global::Gtk.Box.BoxChild w22 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator6]));
 
			w22.PackType = ((global::Gtk.PackType)(1));
 
			w22.Position = 8;
 
			w22.Expand = false;
 
			w22.Fill = false;
 
			this.hbox1.Add (this.vbox8);
 
			global::Gtk.Box.BoxChild w13 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.vbox8]));
 
			w13.Position = 0;
 
			w13.Expand = false;
 
			w13.Fill = false;
 
			global::Gtk.Box.BoxChild w23 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.vbox8]));
 
			w23.Position = 0;
 
			w23.Expand = false;
 
			w23.Fill = false;
 
			// Container child hbox1.Gtk.Box+BoxChild
 
			this.GtkScrolledWindow1 = new global::Gtk.ScrolledWindow ();
 
			this.GtkScrolledWindow1.Name = "GtkScrolledWindow1";
 
			this.GtkScrolledWindow1.ShadowType = ((global::Gtk.ShadowType)(1));
 
			// Container child GtkScrolledWindow1.Gtk.Container+ContainerChild
 
			this.recipeList = new global::Gtk.TreeView ();
 
			this.recipeList.WidthRequest = 300;
 
			this.recipeList.CanFocus = true;
 
			this.recipeList.Name = "recipeList";
 
			this.GtkScrolledWindow1.Add (this.recipeList);
 
			this.hbox1.Add (this.GtkScrolledWindow1);
 
			global::Gtk.Box.BoxChild w15 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.GtkScrolledWindow1]));
 
			w15.Position = 1;
 
			global::Gtk.Box.BoxChild w25 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.GtkScrolledWindow1]));
 
			w25.Position = 1;
 
			// Container child hbox1.Gtk.Box+BoxChild
 
			this.vbox3 = new global::Gtk.VBox ();
 
			this.vbox3.Name = "vbox3";
 
			this.vbox3.Spacing = 6;
 
			// Container child vbox3.Gtk.Box+BoxChild
 
			this.frame2 = new global::Gtk.Frame ();
 
			this.frame2.WidthRequest = 200;
 
			this.frame2.HeightRequest = 200;
 
			this.frame2.Name = "frame2";
 
			this.frame2.ShadowType = ((global::Gtk.ShadowType)(0));
 
			// Container child frame2.Gtk.Container+ContainerChild
 
			this.GtkAlignment = new global::Gtk.Alignment (0F, 0F, 1F, 1F);
 
			this.GtkAlignment.Name = "GtkAlignment";
 
			this.GtkAlignment.LeftPadding = ((uint)(12));
 
			// Container child GtkAlignment.Gtk.Container+ContainerChild
 
			this.scrolledwindow1 = new global::Gtk.ScrolledWindow ();
 
			this.scrolledwindow1.CanFocus = true;
 
			this.scrolledwindow1.Name = "scrolledwindow1";
 
			this.scrolledwindow1.ShadowType = ((global::Gtk.ShadowType)(1));
 
			// Container child scrolledwindow1.Gtk.Container+ContainerChild
 
			global::Gtk.Viewport w16 = new global::Gtk.Viewport ();
 
			w16.ShadowType = ((global::Gtk.ShadowType)(0));
 
			// Container child GtkViewport.Gtk.Container+ContainerChild
 
			this.recipeListBox = new global::Gtk.VBox ();
 
			this.recipeListBox.Name = "recipeListBox";
 
			this.recipeListBox.Homogeneous = true;
 
			this.recipeListBox.Spacing = 6;
 
			this.recipeListBox.BorderWidth = ((uint)(1));
 
			w16.Add (this.recipeListBox);
 
			this.scrolledwindow1.Add (w16);
 
			this.GtkAlignment.Add (this.scrolledwindow1);
 
			this.frame2.Add (this.GtkAlignment);
 
			this.GtkScrolledWindow = new global::Gtk.ScrolledWindow ();
 
			this.GtkScrolledWindow.Name = "GtkScrolledWindow";
 
			this.GtkScrolledWindow.ShadowType = ((global::Gtk.ShadowType)(1));
 
			// Container child GtkScrolledWindow.Gtk.Container+ContainerChild
 
			this.recipeIngredientsView = new global::Gtk.TreeView ();
 
			this.recipeIngredientsView.CanFocus = true;
 
			this.recipeIngredientsView.Name = "recipeIngredientsView";
 
			this.GtkScrolledWindow.Add (this.recipeIngredientsView);
 
			this.frame2.Add (this.GtkScrolledWindow);
 
			this.recipeLabel = new global::Gtk.Label ();
 
			this.recipeLabel.Name = "recipeLabel";
 
			this.recipeLabel.LabelProp = "<b>Recipe</b>";
 
			this.recipeLabel.UseMarkup = true;
 
			this.frame2.LabelWidget = this.recipeLabel;
 
			this.vbox3.Add (this.frame2);
 
			global::Gtk.Box.BoxChild w21 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.frame2]));
 
			w21.Position = 0;
 
			global::Gtk.Box.BoxChild w28 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.frame2]));
 
			w28.Position = 0;
 
			// Container child vbox3.Gtk.Box+BoxChild
 
			this.button919 = new global::Gtk.Button ();
 
			this.button919.CanFocus = true;
 
			this.button919.Name = "button919";
 
			this.button919.UseUnderline = true;
 
			this.button919.Label = "Copy";
 
			this.vbox3.Add (this.button919);
 
			global::Gtk.Box.BoxChild w29 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.button919]));
 
			w29.Position = 1;
 
			w29.Expand = false;
 
			w29.Fill = false;
 
			// Container child vbox3.Gtk.Box+BoxChild
 
			this.paintSwatch = new global::DesertPaintLab.PaintSwatch ();
 
			this.paintSwatch.HeightRequest = 200;
 
			this.paintSwatch.Events = ((global::Gdk.EventMask)(256));
 
			this.paintSwatch.Name = "paintSwatch";
 
			this.vbox3.Add (this.paintSwatch);
 
			global::Gtk.Box.BoxChild w22 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.paintSwatch]));
 
			w22.Position = 1;
 
			global::Gtk.Box.BoxChild w30 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.paintSwatch]));
 
			w30.Position = 2;
 
			this.hbox1.Add (this.vbox3);
 
			global::Gtk.Box.BoxChild w23 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.vbox3]));
 
			w23.Position = 2;
 
			w23.Expand = false;
 
			w23.Fill = false;
 
			global::Gtk.Box.BoxChild w31 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.vbox3]));
 
			w31.Position = 2;
 
			w31.Expand = false;
 
			w31.Fill = false;
 
			this.vbox2.Add (this.hbox1);
 
			global::Gtk.Box.BoxChild w24 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox1]));
 
			w24.Position = 1;
 
			global::Gtk.Box.BoxChild w32 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox1]));
 
			w32.Position = 1;
 
			// Container child vbox2.Gtk.Box+BoxChild
 
			this.hbox3 = new global::Gtk.HBox ();
 
			this.hbox3.Name = "hbox3";
 
			this.hbox3.Spacing = 6;
 
			// Container child hbox3.Gtk.Box+BoxChild
 
			this.hseparator2 = new global::Gtk.HSeparator ();
 
			this.hseparator2.Name = "hseparator2";
 
			this.hbox3.Add (this.hseparator2);
 
			global::Gtk.Box.BoxChild w25 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.hseparator2]));
 
			w25.Position = 0;
 
			global::Gtk.Box.BoxChild w33 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.hseparator2]));
 
			w33.Position = 0;
 
			// Container child hbox3.Gtk.Box+BoxChild
 
			this.stopResumeButton = new global::Gtk.Button ();
 
			this.stopResumeButton.Sensitive = false;
 
			this.stopResumeButton.CanFocus = true;
 
			this.stopResumeButton.Name = "stopResumeButton";
 
			this.stopResumeButton.UseUnderline = true;
 
			this.stopResumeButton.Label = "Stop";
 
			this.hbox3.Add (this.stopResumeButton);
 
			global::Gtk.Box.BoxChild w26 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.stopResumeButton]));
 
			w26.Position = 1;
 
			w26.Expand = false;
 
			w26.Fill = false;
 
			w26.Padding = ((uint)(20));
 
			global::Gtk.Box.BoxChild w34 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.stopResumeButton]));
 
			w34.Position = 1;
 
			w34.Expand = false;
 
			w34.Fill = false;
 
			w34.Padding = ((uint)(20));
 
			// Container child hbox3.Gtk.Box+BoxChild
 
			this.countLabel = new global::Gtk.Label ();
 
			this.countLabel.Name = "countLabel";
 
			this.countLabel.LabelProp = "0/192";
 
			this.hbox3.Add (this.countLabel);
 
			global::Gtk.Box.BoxChild w27 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.countLabel]));
 
			w27.Position = 2;
 
			w27.Expand = false;
 
			w27.Fill = false;
 
			w27.Padding = ((uint)(40));
 
			global::Gtk.Box.BoxChild w35 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.countLabel]));
 
			w35.Position = 2;
 
			w35.Expand = false;
 
			w35.Fill = false;
 
			w35.Padding = ((uint)(40));
 
			// Container child hbox3.Gtk.Box+BoxChild
 
			this.beginButton = new global::Gtk.Button ();
 
			this.beginButton.CanFocus = true;
 
			this.beginButton.Name = "beginButton";
 
			this.beginButton.UseUnderline = true;
 
			this.beginButton.Label = "Begin";
 
			this.hbox3.Add (this.beginButton);
 
			global::Gtk.Box.BoxChild w28 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.beginButton]));
 
			w28.Position = 3;
 
			w28.Expand = false;
 
			w28.Fill = false;
 
			w28.Padding = ((uint)(20));
 
			global::Gtk.Box.BoxChild w36 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.beginButton]));
 
			w36.Position = 3;
 
			w36.Expand = false;
 
			w36.Fill = false;
 
			w36.Padding = ((uint)(20));
 
			// Container child hbox3.Gtk.Box+BoxChild
 
			this.hseparator1 = new global::Gtk.HSeparator ();
 
			this.hseparator1.Name = "hseparator1";
 
			this.hbox3.Add (this.hseparator1);
 
			global::Gtk.Box.BoxChild w29 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.hseparator1]));
 
			w29.Position = 4;
 
			global::Gtk.Box.BoxChild w37 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.hseparator1]));
 
			w37.Position = 4;
 
			this.vbox2.Add (this.hbox3);
 
			global::Gtk.Box.BoxChild w30 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox3]));
 
			w30.Position = 2;
 
			w30.Expand = false;
 
			w30.Fill = false;
 
			global::Gtk.Box.BoxChild w38 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox3]));
 
			w38.Position = 2;
 
			w38.Expand = false;
 
			w38.Fill = false;
 
			// Container child vbox2.Gtk.Box+BoxChild
 
			this.statusLabel = new global::Gtk.Label ();
 
			this.statusLabel.Name = "statusLabel";
 
			this.vbox2.Add (this.statusLabel);
 
			global::Gtk.Box.BoxChild w31 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.statusLabel]));
 
			w31.PackType = ((global::Gtk.PackType)(1));
 
			w31.Position = 3;
 
			w31.Expand = false;
 
			w31.Fill = false;
 
			global::Gtk.Box.BoxChild w39 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.statusLabel]));
 
			w39.PackType = ((global::Gtk.PackType)(1));
 
			w39.Position = 3;
 
			w39.Expand = false;
 
			w39.Fill = false;
 
			// Container child vbox2.Gtk.Box+BoxChild
 
			this.progressBar = new global::Gtk.ProgressBar ();
 
			this.progressBar.Name = "progressBar";
 
			this.vbox2.Add (this.progressBar);
 
			global::Gtk.Box.BoxChild w32 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.progressBar]));
 
			w32.PackType = ((global::Gtk.PackType)(1));
 
			w32.Position = 4;
 
			w32.Expand = false;
 
			w32.Fill = false;
 
			global::Gtk.Box.BoxChild w40 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.progressBar]));
 
			w40.PackType = ((global::Gtk.PackType)(1));
 
			w40.Position = 4;
 
			w40.Expand = false;
 
			w40.Fill = false;
 
			this.Add (this.vbox2);
 
			if ((this.Child != null)) {
 
				this.Child.ShowAll ();
 
			}
 
			this.DefaultWidth = 887;
 
			this.DefaultHeight = 570;
 
			this.DefaultHeight = 577;
 
			this.Show ();
 
			this.ExportToWikiAction.Activated += new global::System.EventHandler (this.OnExportToWiki);
 
			this.IngredientsAction.Activated += new global::System.EventHandler (this.OnShowIngredients);
 
			this.CopyToClipboardAction.Activated += new global::System.EventHandler (this.OnCopyRecipeListToClipboard);
 
			this.maxIngredientsSpinButton.ValueChanged += new global::System.EventHandler (this.OnMaxIngredientsChanged);
 
			this.maxRecipeSpinButton.ValueChanged += new global::System.EventHandler (this.OnMaxRecipeChanged);
 
			this.fullQuantityDepthSpinButton.ValueChanged += new global::System.EventHandler (this.OnFullQuantityDepthChanged);
 
			this.fullQuantitySpinButton.Input += new global::Gtk.InputHandler (this.OnFullQuantityChanged);
 
			this.fullQuantitySpinButton.ValueChanged += new global::System.EventHandler (this.OnFullQuantityChanged);
 
			this.button919.Clicked += new global::System.EventHandler (this.OnCopyRecipeToClipboard);
 
			this.stopResumeButton.Clicked += new global::System.EventHandler (this.OnStopResume);
 
			this.beginButton.Clicked += new global::System.EventHandler (this.OnBegin);
 
		}
 
	}
 
}
gtk-gui/gui.stetic
Show inline comments
...
 
@@ -1133,351 +1133,460 @@ You can either import an existing Practi
 
        <child>
 
          <widget class="Gtk.Viewport" id="GtkViewport">
 
            <property name="MemberName" />
 
            <property name="ShadowType">None</property>
 
            <child>
 
              <widget class="Gtk.VBox" id="resultbox">
 
                <property name="MemberName" />
 
                <property name="Spacing">6</property>
 
                <child>
 
                  <placeholder />
 
                </child>
 
                <child>
 
                  <placeholder />
 
                </child>
 
                <child>
 
                  <placeholder />
 
                </child>
 
              </widget>
 
            </child>
 
          </widget>
 
        </child>
 
      </widget>
 
    </child>
 
  </widget>
 
  <widget class="Gtk.Window" id="DesertPaintLab.RecipeGeneratorWindow" design-size="887 570">
 
  <widget class="Gtk.Window" id="DesertPaintLab.RecipeGeneratorWindow" design-size="887 577">
 
    <action-group name="Default">
 
      <action id="ExportAction">
 
        <property name="Type">Action</property>
 
        <property name="Label" translatable="yes">Export</property>
 
        <property name="ShortLabel" translatable="yes">Export</property>
 
      </action>
 
      <action id="ExportToWikiAction">
 
        <property name="Type">Action</property>
 
        <property name="Label" translatable="yes">Export to Wiki</property>
 
        <property name="ShortLabel" translatable="yes">Export to Wiki</property>
 
        <signal name="Activated" handler="OnExportToWiki" />
 
      </action>
 
      <action id="SettingsAction">
 
        <property name="Type">Action</property>
 
        <property name="Label" translatable="yes">Settings</property>
 
        <property name="ShortLabel" translatable="yes">Tools</property>
 
      </action>
 
      <action id="IngredientsAction">
 
        <property name="Type">Action</property>
 
        <property name="Label" translatable="yes">Ingredients</property>
 
        <property name="ShortLabel" translatable="yes">Ingredients</property>
 
        <signal name="Activated" handler="OnShowIngredients" />
 
      </action>
 
      <action id="CopyToClipboardAction">
 
        <property name="Type">Action</property>
 
        <property name="Label" translatable="yes">Copy to Clipboard</property>
 
        <property name="ShortLabel" translatable="yes">Copy to Clipboard</property>
 
        <signal name="Activated" handler="OnCopyRecipeListToClipboard" />
 
      </action>
 
    </action-group>
 
    <property name="MemberName" />
 
    <property name="Title" translatable="yes">Recipe Generator</property>
 
    <property name="WindowPosition">CenterOnParent</property>
 
    <child>
 
      <widget class="Gtk.VBox" id="vbox2">
 
        <property name="MemberName" />
 
        <property name="Spacing">6</property>
 
        <property name="BorderWidth">8</property>
 
        <child>
 
          <widget class="Gtk.MenuBar" id="menubar1">
 
            <property name="MemberName" />
 
            <node name="__gtksharp_64_Stetic_Editor_ActionMenuBar" type="Menubar">
 
              <node type="Menu" action="ExportAction">
 
                <node type="Menuitem" action="ExportToWikiAction" />
 
              </node>
 
              <node type="Menu" action="SettingsAction">
 
                <node type="Menuitem" action="IngredientsAction" />
 
                <node type="Menuitem" action="CopyToClipboardAction" />
 
              </node>
 
            </node>
 
          </widget>
 
          <packing>
 
            <property name="Position">0</property>
 
            <property name="AutoSize">True</property>
 
            <property name="Expand">False</property>
 
            <property name="Fill">False</property>
 
          </packing>
 
        </child>
 
        <child>
 
          <widget class="Gtk.HBox" id="hbox1">
 
            <property name="MemberName" />
 
            <property name="Spacing">6</property>
 
            <child>
 
              <widget class="Gtk.VBox" id="vbox8">
 
                <property name="MemberName" />
 
                <property name="Spacing">6</property>
 
                <child>
 
                  <widget class="Gtk.Label" id="label3">
 
                  <widget class="Gtk.HBox" id="hbox2">
 
                    <property name="MemberName" />
 
                    <property name="LabelProp" translatable="yes">Maximum Ingredients</property>
 
                    <property name="Spacing">6</property>
 
                    <child>
 
                      <widget class="Gtk.SpinButton" id="maxIngredientsSpinButton">
 
                        <property name="MemberName" />
 
                        <property name="CanFocus">True</property>
 
                        <property name="Upper">14</property>
 
                        <property name="PageIncrement">10</property>
 
                        <property name="StepIncrement">1</property>
 
                        <property name="ClimbRate">1</property>
 
                        <property name="Numeric">True</property>
 
                        <signal name="ValueChanged" handler="OnMaxIngredientsChanged" after="yes" />
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">0</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                    <child>
 
                      <widget class="Gtk.Label" id="label3">
 
                        <property name="MemberName" />
 
                        <property name="LabelProp" translatable="yes">Maximum Ingredients</property>
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">1</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">0</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.SpinButton" id="maxIngredientsSpinButton">
 
                    <property name="MemberName" />
 
                    <property name="CanFocus">True</property>
 
                    <property name="Upper">14</property>
 
                    <property name="PageIncrement">10</property>
 
                    <property name="StepIncrement">1</property>
 
                    <property name="ClimbRate">1</property>
 
                    <property name="Numeric">True</property>
 
                    <signal name="ValueChanged" handler="OnMaxIngredientsChanged" />
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">1</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.HSeparator" id="hseparator3">
 
                    <property name="MemberName" />
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">1</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.HBox" id="hbox4">
 
                    <property name="MemberName" />
 
                    <property name="Spacing">6</property>
 
                    <child>
 
                      <widget class="Gtk.SpinButton" id="maxRecipeSpinButton">
 
                        <property name="MemberName" />
 
                        <property name="CanFocus">True</property>
 
                        <property name="Upper">100</property>
 
                        <property name="PageIncrement">10</property>
 
                        <property name="StepIncrement">1</property>
 
                        <property name="ClimbRate">1</property>
 
                        <property name="Numeric">True</property>
 
                        <signal name="ValueChanged" handler="OnMaxRecipeChanged" after="yes" />
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">0</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                    <child>
 
                      <widget class="Gtk.Label" id="label4">
 
                        <property name="MemberName" />
 
                        <property name="LabelProp" translatable="yes">Max Total Quantity</property>
 
                        <property name="UseMarkup">True</property>
 
                        <property name="Wrap">True</property>
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">1</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">2</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.Label" id="label4">
 
                  <widget class="Gtk.HSeparator" id="hseparator4">
 
                    <property name="MemberName" />
 
                    <property name="LabelProp" translatable="yes">Max Total Quantity</property>
 
                    <property name="UseMarkup">True</property>
 
                    <property name="Wrap">True</property>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">3</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                    <property name="Padding">8</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.SpinButton" id="maxRecipeSpinButton">
 
                  <widget class="Gtk.HBox" id="hbox5">
 
                    <property name="MemberName" />
 
                    <property name="CanFocus">True</property>
 
                    <property name="Upper">100</property>
 
                    <property name="PageIncrement">10</property>
 
                    <property name="StepIncrement">1</property>
 
                    <property name="ClimbRate">1</property>
 
                    <property name="Numeric">True</property>
 
                    <signal name="ValueChanged" handler="OnMaxRecipeChanged" />
 
                    <property name="Spacing">6</property>
 
                    <child>
 
                      <widget class="Gtk.SpinButton" id="fullQuantityDepthSpinButton">
 
                        <property name="MemberName" />
 
                        <property name="CanFocus">True</property>
 
                        <property name="Upper">15</property>
 
                        <property name="PageIncrement">10</property>
 
                        <property name="StepIncrement">1</property>
 
                        <property name="ClimbRate">1</property>
 
                        <property name="Numeric">True</property>
 
                        <signal name="ValueChanged" handler="OnFullQuantityDepthChanged" after="yes" />
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">0</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                    <child>
 
                      <widget class="Gtk.Label" id="label8">
 
                        <property name="MemberName" />
 
                        <property name="LabelProp" translatable="yes">Full Quantity Depth</property>
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">1</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">4</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.HSeparator" id="hseparator4">
 
                  <widget class="Gtk.HSeparator" id="hseparator5">
 
                    <property name="MemberName" />
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">5</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.Label" id="label8">
 
                  <widget class="Gtk.HBox" id="hbox6">
 
                    <property name="MemberName" />
 
                    <property name="LabelProp" translatable="yes">Full Quantity Depth</property>
 
                    <property name="Spacing">6</property>
 
                    <child>
 
                      <widget class="Gtk.SpinButton" id="fullQuantitySpinButton">
 
                        <property name="MemberName" />
 
                        <property name="CanFocus">True</property>
 
                        <property name="Upper">30</property>
 
                        <property name="PageIncrement">10</property>
 
                        <property name="StepIncrement">1</property>
 
                        <property name="ClimbRate">1</property>
 
                        <property name="Numeric">True</property>
 
                        <signal name="ValueChanged" handler="OnFullQuantityChanged" />
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">0</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                    <child>
 
                      <widget class="Gtk.Label" id="label9">
 
                        <property name="MemberName" />
 
                        <property name="LabelProp" translatable="yes">FullQuantity</property>
 
                      </widget>
 
                      <packing>
 
                        <property name="Position">1</property>
 
                        <property name="AutoSize">True</property>
 
                        <property name="Expand">False</property>
 
                        <property name="Fill">False</property>
 
                      </packing>
 
                    </child>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">6</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.SpinButton" id="fullQuantityDepthSpinButton">
 
                  <widget class="Gtk.Frame" id="frame3">
 
                    <property name="MemberName" />
 
                    <property name="CanFocus">True</property>
 
                    <property name="Upper">15</property>
 
                    <property name="PageIncrement">10</property>
 
                    <property name="StepIncrement">1</property>
 
                    <property name="ClimbRate">1</property>
 
                    <property name="Numeric">True</property>
 
                    <signal name="ValueChanged" handler="OnFullQuantityDepthChanged" />
 
                    <property name="ShadowType">None</property>
 
                    <child>
 
                      <widget class="Gtk.Alignment" id="GtkAlignment1">
 
                        <property name="MemberName" />
 
                        <property name="Xalign">0</property>
 
                        <property name="Yalign">0</property>
 
                        <property name="LeftPadding">12</property>
 
                        <child>
 
                          <widget class="Gtk.ScrolledWindow" id="scrolledwindow2">
 
                            <property name="MemberName" />
 
                            <property name="CanFocus">True</property>
 
                            <property name="ShadowType">In</property>
 
                            <child>
 
                              <widget class="Gtk.TreeView" id="reagentListView">
 
                                <property name="MemberName" />
 
                                <property name="WidthRequest">300</property>
 
                                <property name="CanFocus">True</property>
 
                              </widget>
 
                            </child>
 
                          </widget>
 
                        </child>
 
                      </widget>
 
                    </child>
 
                    <child>
 
                      <widget class="Gtk.Label" id="GtkLabel4">
 
                        <property name="MemberName" />
 
                        <property name="LabelProp" translatable="yes">&lt;b&gt;Ingredients&lt;/b&gt;</property>
 
                        <property name="UseMarkup">True</property>
 
                      </widget>
 
                      <packing>
 
                        <property name="type">label_item</property>
 
                      </packing>
 
                    </child>
 
                  </widget>
 
                  <packing>
 
                    <property name="PackType">End</property>
 
                    <property name="Position">7</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.Label" id="label9">
 
                  <widget class="Gtk.HSeparator" id="hseparator6">
 
                    <property name="MemberName" />
 
                    <property name="LabelProp" translatable="yes">FullQuantity</property>
 
                  </widget>
 
                  <packing>
 
                    <property name="PackType">End</property>
 
                    <property name="Position">8</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.SpinButton" id="fullQuantitySpinButton">
 
                    <property name="MemberName" />
 
                    <property name="CanFocus">True</property>
 
                    <property name="Upper">30</property>
 
                    <property name="PageIncrement">10</property>
 
                    <property name="StepIncrement">1</property>
 
                    <property name="ClimbRate">1</property>
 
                    <property name="Numeric">True</property>
 
                    <signal name="Input" handler="OnFullQuantityChanged" />
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">9</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
              </widget>
 
              <packing>
 
                <property name="Position">0</property>
 
                <property name="AutoSize">True</property>
 
                <property name="Expand">False</property>
 
                <property name="Fill">False</property>
 
              </packing>
 
            </child>
 
            <child>
 
              <widget class="Gtk.ScrolledWindow" id="GtkScrolledWindow1">
 
                <property name="MemberName" />
 
                <property name="ShadowType">In</property>
 
                <child>
 
                  <widget class="Gtk.TreeView" id="recipeList">
 
                    <property name="MemberName" />
 
                    <property name="WidthRequest">300</property>
 
                    <property name="CanFocus">True</property>
 
                    <property name="ShowScrollbars">True</property>
 
                  </widget>
 
                </child>
 
              </widget>
 
              <packing>
 
                <property name="Position">1</property>
 
                <property name="AutoSize">True</property>
 
              </packing>
 
            </child>
 
            <child>
 
              <widget class="Gtk.VBox" id="vbox3">
 
                <property name="MemberName" />
 
                <property name="Spacing">6</property>
 
                <child>
 
                  <widget class="Gtk.Frame" id="frame2">
 
                    <property name="MemberName" />
 
                    <property name="WidthRequest">200</property>
 
                    <property name="HeightRequest">200</property>
 
                    <property name="ShadowType">None</property>
 
                    <child>
 
                      <widget class="Gtk.Alignment" id="GtkAlignment">
 
                      <widget class="Gtk.ScrolledWindow" id="GtkScrolledWindow">
 
                        <property name="MemberName" />
 
                        <property name="Xalign">0</property>
 
                        <property name="Yalign">0</property>
 
                        <property name="LeftPadding">12</property>
 
                        <property name="ShadowType">In</property>
 
                        <child>
 
                          <widget class="Gtk.ScrolledWindow" id="scrolledwindow1">
 
                          <widget class="Gtk.TreeView" id="recipeIngredientsView">
 
                            <property name="MemberName" />
 
                            <property name="CanFocus">True</property>
 
                            <property name="ShadowType">In</property>
 
                            <child>
 
                              <widget class="Gtk.Viewport" id="GtkViewport">
 
                                <property name="MemberName" />
 
                                <property name="ShadowType">None</property>
 
                                <child>
 
                                  <widget class="Gtk.VBox" id="recipeListBox">
 
                                    <property name="MemberName" />
 
                                    <property name="Homogeneous">True</property>
 
                                    <property name="Spacing">6</property>
 
                                    <property name="BorderWidth">1</property>
 
                                    <child>
 
                                      <placeholder />
 
                                    </child>
 
                                    <child>
 
                                      <placeholder />
 
                                    </child>
 
                                    <child>
 
                                      <placeholder />
 
                                    </child>
 
                                  </widget>
 
                                </child>
 
                              </widget>
 
                            </child>
 
                            <property name="ShowScrollbars">True</property>
 
                          </widget>
 
                        </child>
 
                      </widget>
 
                    </child>
 
                    <child>
 
                      <widget class="Gtk.Label" id="recipeLabel">
 
                        <property name="MemberName" />
 
                        <property name="LabelProp" translatable="yes">&lt;b&gt;Recipe&lt;/b&gt;</property>
 
                        <property name="UseMarkup">True</property>
 
                      </widget>
 
                      <packing>
 
                        <property name="type">label_item</property>
 
                      </packing>
 
                    </child>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">0</property>
 
                    <property name="AutoSize">True</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="Gtk.Button" id="button919">
 
                    <property name="MemberName" />
 
                    <property name="CanFocus">True</property>
 
                    <property name="Type">TextOnly</property>
 
                    <property name="Label" translatable="yes">Copy</property>
 
                    <property name="UseUnderline">True</property>
 
                    <signal name="Clicked" handler="OnCopyRecipeToClipboard" />
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">1</property>
 
                    <property name="AutoSize">True</property>
 
                    <property name="Expand">False</property>
 
                    <property name="Fill">False</property>
 
                  </packing>
 
                </child>
 
                <child>
 
                  <widget class="DesertPaintLab.PaintSwatch" id="paintSwatch">
 
                    <property name="MemberName" />
 
                    <property name="HeightRequest">200</property>
 
                    <property name="Events">ButtonPressMask</property>
 
                  </widget>
 
                  <packing>
 
                    <property name="Position">1</property>
 
                    <property name="Position">2</property>
 
                    <property name="AutoSize">True</property>
 
                  </packing>
 
                </child>
 
              </widget>
 
              <packing>
 
                <property name="Position">2</property>
 
                <property name="AutoSize">True</property>
 
                <property name="Expand">False</property>
 
                <property name="Fill">False</property>
 
              </packing>
 
            </child>
 
          </widget>
 
          <packing>
 
            <property name="Position">1</property>
 
            <property name="AutoSize">False</property>
 
          </packing>
 
        </child>
 
        <child>
 
          <widget class="Gtk.HBox" id="hbox3">
 
            <property name="MemberName" />
 
            <property name="Spacing">6</property>
 
            <child>
 
              <widget class="Gtk.HSeparator" id="hseparator2">
 
                <property name="MemberName" />
0 comments (0 inline, 0 general)