diff --git a/cs/TagCloud/CircularCloudLayouter.cs b/cs/TagCloud/CircularCloudLayouter.cs new file mode 100644 index 000000000..6974030ac --- /dev/null +++ b/cs/TagCloud/CircularCloudLayouter.cs @@ -0,0 +1,38 @@ +using System.Drawing; + +namespace TagCloud; + +public class CircularCloudLayouter +{ + private List rectangles; + private Point center; + private ICloudShaper shaper; + + public IEnumerable Rectangles => rectangles; + + public CircularCloudLayouter(Point center) + { + this.center = center; + rectangles = new List(); + shaper = SpiralCloudShaper.Create(this.center); + } + + public Rectangle PutNextRectangle(Size size) + { + if (size.Width <= 0) + throw new ArgumentException("Size width must be positive number"); + if (size.Height <= 0) + throw new ArgumentException("Size height must be positive number"); + + Rectangle rectangle = Rectangle.Empty; + foreach (var point in shaper.GetPossiblePoints()) + { + rectangle = new Rectangle(point, size); + if (!Rectangles.Any(rect => rect.IntersectsWith(rectangle))) + break; + } + + rectangles.Add(rectangle); + return rectangle; + } +} \ No newline at end of file diff --git a/cs/TagCloud/CloudDrawers/CloudDrawer.cs b/cs/TagCloud/CloudDrawers/CloudDrawer.cs new file mode 100644 index 000000000..d9673f5b4 --- /dev/null +++ b/cs/TagCloud/CloudDrawers/CloudDrawer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagCloudTests; + +public interface ICloudDrawer +{ + void Draw(IEnumerable rectangle, string name); +} \ No newline at end of file diff --git a/cs/TagCloud/CloudDrawers/TagCloudDrawer.cs b/cs/TagCloud/CloudDrawers/TagCloudDrawer.cs new file mode 100644 index 000000000..493c9874a --- /dev/null +++ b/cs/TagCloud/CloudDrawers/TagCloudDrawer.cs @@ -0,0 +1,52 @@ +using System.Drawing; +using System.Dynamic; +using TagCloudTests; + +namespace TagCloud; + +public class TagCloudDrawer : ICloudDrawer +{ + private readonly string path; + private readonly IColorSelector selector; + + private TagCloudDrawer(string path, IColorSelector selector) + { + this.path = path; + this.selector = selector; + } + + public void Draw(IEnumerable rectangles, string name) + { + if (rectangles.Count() == 0) + throw new ArgumentException("Empty rectangles list"); + + var minX = rectangles.Min(rect => rect.X); + var maxX = rectangles.Max(rect => rect.Right); + var minY = rectangles.Min(rect => rect.Top); + var maxY = rectangles.Max(rect => rect.Bottom); + + using var bitmap = new Bitmap(maxX - minX + 2, maxY - minY + 2); + using var graphics = Graphics.FromImage(bitmap); + graphics.DrawRectangles( + selector, + rectangles + .Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) + .ToArray() + ); + SaveToFile(bitmap, name); + } + + private void SaveToFile(Bitmap bitmap, string name) + { + var pathToFile = @$"{path}\{name}.jpg"; + bitmap.Save(pathToFile); + Console.WriteLine($"Tag cloud visualization saved to file {path}"); + } + + public static TagCloudDrawer Create(string path, IColorSelector selector) + { + if (!Directory.Exists(path)) + throw new ArgumentException("Directory does not exist"); + return new TagCloudDrawer(path, selector); + } +} \ No newline at end of file diff --git a/cs/TagCloud/ColorSelectors/ConstantColorSelector.cs b/cs/TagCloud/ColorSelectors/ConstantColorSelector.cs new file mode 100644 index 000000000..35a1ff03a --- /dev/null +++ b/cs/TagCloud/ColorSelectors/ConstantColorSelector.cs @@ -0,0 +1,15 @@ +using System.Drawing; +using TagCloudTests; + +namespace TagCloud; + +public class ConstantColorSelector : IColorSelector +{ + private Color color; + public ConstantColorSelector(Color color) + { + this.color = color; + } + + public Color PickColor() => color; +} \ No newline at end of file diff --git a/cs/TagCloud/ColorSelectors/IColorSelector.cs b/cs/TagCloud/ColorSelectors/IColorSelector.cs new file mode 100644 index 000000000..324d62365 --- /dev/null +++ b/cs/TagCloud/ColorSelectors/IColorSelector.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagCloudTests; + +public interface IColorSelector +{ + Color PickColor(); +} \ No newline at end of file diff --git a/cs/TagCloud/ColorSelectors/RandomColorSelector.cs b/cs/TagCloud/ColorSelectors/RandomColorSelector.cs new file mode 100644 index 000000000..0afb3dcff --- /dev/null +++ b/cs/TagCloud/ColorSelectors/RandomColorSelector.cs @@ -0,0 +1,22 @@ +using System.Drawing; + +namespace TagCloudTests; + +public class RandomColorSelector : IColorSelector +{ + private readonly Random random; + + public RandomColorSelector() + { + random = new Random(DateTime.Now.Microsecond); + } + + public Color PickColor() + { + return Color.FromArgb(255, + random.Next(0, 255), + random.Next(0, 255), + random.Next(0, 255) + ); + } +} \ No newline at end of file diff --git a/cs/TagCloud/Extensions.cs b/cs/TagCloud/Extensions.cs new file mode 100644 index 000000000..02258548d --- /dev/null +++ b/cs/TagCloud/Extensions.cs @@ -0,0 +1,17 @@ +using System.Drawing; +using TagCloudTests; + +namespace TagCloud; + +public static class Extensions +{ + public static void DrawRectangles(this Graphics graphics, IColorSelector selector, Rectangle[] rectangles, int lineWidth = 1) + { + var pen = new Pen(selector.PickColor(), lineWidth); + foreach (var rectangle in rectangles) + { + graphics.DrawRectangle(pen, rectangle); + pen.Color = selector.PickColor(); + } + } +} \ No newline at end of file diff --git a/cs/TagCloud/ICloudShaper.cs b/cs/TagCloud/ICloudShaper.cs new file mode 100644 index 000000000..67c8e063e --- /dev/null +++ b/cs/TagCloud/ICloudShaper.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagCloud; + +public interface ICloudShaper +{ + IEnumerable GetPossiblePoints(); +} \ No newline at end of file diff --git a/cs/TagCloud/SpiralCloudShaper.cs b/cs/TagCloud/SpiralCloudShaper.cs new file mode 100644 index 000000000..6a27f1876 --- /dev/null +++ b/cs/TagCloud/SpiralCloudShaper.cs @@ -0,0 +1,53 @@ +using System.Drawing; + +namespace TagCloud; + +public class SpiralCloudShaper : ICloudShaper +{ + private Point center; + private double coefficient; + private double deltaAngle; + + private SpiralCloudShaper(Point center, double coefficient, double deltaAngle) + { + this.center = center; + this.deltaAngle = deltaAngle; + this.coefficient = coefficient; + } + + public IEnumerable GetPossiblePoints() + { + var currentAngle = 0D; + var position = center; + var previousPoint = position; + while(true) + { + while (position == previousPoint) + { + currentAngle += deltaAngle; + previousPoint = position; + position = CalculatePointByCurrentAngle(currentAngle); + } + yield return position; + previousPoint = position; + position = CalculatePointByCurrentAngle(currentAngle); + } + } + + private Point CalculatePointByCurrentAngle(double angle) + { + return new Point( + center.X + (int)(coefficient * angle * Math.Cos(angle)), + center.Y + (int)(coefficient * angle * Math.Sin(angle)) + ); + } + + public static SpiralCloudShaper Create(Point center, double coefficient = 0.1, double deltaAngle = 0.1) + { + if (coefficient <= 0) + throw new ArgumentException("Spiral coefficient must be positive number"); + if (deltaAngle <= 0) + throw new ArgumentException("Spiral delta angle must be positive number"); + return new SpiralCloudShaper(center, coefficient, deltaAngle); + } +} \ No newline at end of file diff --git a/cs/TagCloud/TagCloud.csproj b/cs/TagCloud/TagCloud.csproj new file mode 100644 index 000000000..e298c8464 --- /dev/null +++ b/cs/TagCloud/TagCloud.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/cs/TagCloudTests/CircularCloudLayouterTests.cs b/cs/TagCloudTests/CircularCloudLayouterTests.cs new file mode 100644 index 000000000..b402ad336 --- /dev/null +++ b/cs/TagCloudTests/CircularCloudLayouterTests.cs @@ -0,0 +1,119 @@ +using System.Drawing; +using FluentAssertions; +using NUnit.Framework.Interfaces; +using TagCloud; + +namespace TagCloudTests; + +public class CircularCloudLayouterTests +{ + private const string RelativePathToFailDirectory = @"..\..\..\Fails"; + + private CircularCloudLayouter layouter; + private Point center; + private ICloudDrawer drawer; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + drawer = TagCloudDrawer.Create(Path.GetFullPath(RelativePathToFailDirectory), new RandomColorSelector()); + } + + [SetUp] + public void Setup() + { + center = new Point(0, 0); + layouter = new CircularCloudLayouter(center); + } + + [TearDown] + public void Tear_Down() + { + if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) + drawer.Draw(layouter.Rectangles, TestContext.CurrentContext.Test.FullName); + } + + [Test] + public void ReturnEmptyList_WhenCreated() + { + layouter.Rectangles.Should().BeEmpty(); + } + + [Test] + public void ReturnOneElementList_WhenAddOne() + { + layouter.PutNextRectangle(new Size(1, 1)); + layouter.Rectangles.Count().Should().Be(1); + } + + [Test] + public void ReturnTwoElementList_WhenAddTwo() + { + layouter.PutNextRectangle(new Size(1, 1)); + layouter.PutNextRectangle(new Size(1, 1)); + layouter.Rectangles.Count().Should().Be(2); + NotIntersectedAssert(layouter.Rectangles); + } + + [TestCase(1, 1, 500, 0.77D, TestName = "WithSquareShape")] + [TestCase(20, 10, 500, 0.67D, TestName = "WithRectangleShape")] + public void AddManyNotIntersectedRectangles_WithConstantSize_ByCloudShape(int width, int height, int count, double accuracy) + { + var size = new Size(width, height); + for (int i = 0; i < count; i++) + layouter.PutNextRectangle(size); + + layouter.Rectangles.Count().Should().Be(count); + NotIntersectedAssert(layouter.Rectangles); + CircleShapeAssertion(layouter.Rectangles, accuracy); + } + + [Test] + public void AddManyNotIntersectedRectangles_WithVariableSize_ByCloudShape() + { + var rnd = new Random(DateTime.Now.Microsecond); + + for (int i = 0; i < 200; i++) + { + var size = new Size(rnd.Next(20, 40), rnd.Next(20, 40)); + layouter.PutNextRectangle(size); + } + + layouter.Rectangles.Count().Should().Be(200); + NotIntersectedAssert(layouter.Rectangles); + CircleShapeAssertion(layouter.Rectangles, 0.6D); + } + + [TestCase(-1, 1, TestName = "WithNegativeWidth")] + [TestCase(1, -1, TestName = "WithNegativeHeight")] + [TestCase(-1, -1, TestName = "WithNegativeWidthAndHeight")] + public void Throw_ThenTryPutRectangle(int width, int height) + { + Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); + } + + private void CircleShapeAssertion(IEnumerable rectangles, double accuracy) + { + var circleRadius = rectangles + .SelectMany(rect => new[] + { + rect.Location, + rect.Location with { X = rect.Right }, + rect.Location with { Y = rect.Bottom }, + rect.Location with { X = rect.Right, Y = rect.Bottom } + }) + .Max(point => point.GetDistanceTo(center)); + Console.WriteLine(circleRadius); + var containingCircleRadius = Math.PI * circleRadius * circleRadius; + var rectanglesTotalArea = rectangles.Sum(rect => rect.Width * rect.Height); + Assert.GreaterOrEqual(rectanglesTotalArea / containingCircleRadius, accuracy); + } + + public static void NotIntersectedAssert(IEnumerable rectangles) + { + rectangles + .HasIntersectedRectangles() + .Should() + .BeFalse(); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/Examples/TagCloudTests.CircularCloudLayouterTests.WithSquareShape.jpg b/cs/TagCloudTests/Examples/TagCloudTests.CircularCloudLayouterTests.WithSquareShape.jpg new file mode 100644 index 000000000..82d7b5c79 Binary files /dev/null and b/cs/TagCloudTests/Examples/TagCloudTests.CircularCloudLayouterTests.WithSquareShape.jpg differ diff --git a/cs/TagCloudTests/Extensions.cs b/cs/TagCloudTests/Extensions.cs new file mode 100644 index 000000000..6f7b88c79 --- /dev/null +++ b/cs/TagCloudTests/Extensions.cs @@ -0,0 +1,32 @@ +using System.Diagnostics.Metrics; +using System.Drawing; + +namespace TagCloudTests; + +public static class Extensions +{ + public static IEnumerable> CartesianProduct(this IEnumerable source) + { + var array = source.ToArray(); + if (array.Length <= 1) + throw new ArgumentException(); + for (int i = 0; i < array.Length; i++) + for (int j = i + 1; j < array.Length; j++) + yield return Tuple.Create(array[i], array[j]); + } + + public static bool HasIntersectedRectangles(this IEnumerable rectangles) + { + return rectangles + .SelectMany( + (x, i) => rectangles.Skip(i + 1), + (x, y) => Tuple.Create(x, y) + ) + .Any(tuple => tuple.Item1.IntersectsWith(tuple.Item2)); + } + + public static double GetDistanceTo(this Point first, Point second) + { + return Math.Sqrt((first.X - second.X) * (first.X - second.X) + (first.Y - second.Y) * (first.Y - second.Y)); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/Fails/TagCloudTests.CloudTests_Should.AlwaysFail(5,10,10,20,100).jpg b/cs/TagCloudTests/Fails/TagCloudTests.CloudTests_Should.AlwaysFail(5,10,10,20,100).jpg new file mode 100644 index 000000000..79ee9f89c Binary files /dev/null and b/cs/TagCloudTests/Fails/TagCloudTests.CloudTests_Should.AlwaysFail(5,10,10,20,100).jpg differ diff --git a/cs/TagCloudTests/GlobalUsings.cs b/cs/TagCloudTests/GlobalUsings.cs new file mode 100644 index 000000000..cefced496 --- /dev/null +++ b/cs/TagCloudTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/cs/TagCloudTests/HasIntersectedRectangleTests.cs b/cs/TagCloudTests/HasIntersectedRectangleTests.cs new file mode 100644 index 000000000..7eb4627c8 --- /dev/null +++ b/cs/TagCloudTests/HasIntersectedRectangleTests.cs @@ -0,0 +1,29 @@ +using System.Drawing; +using FluentAssertions; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TagCloudTests.TestData; + +namespace TagCloudTests; + +public class HasIntersectedRectangleTests +{ + [TestCaseSource(typeof(IntersectedRectanglesTestCases), nameof(IntersectedRectanglesTestCases.Data))] + [TestCaseSource(typeof(NotIntersectedRectanglesTestCases), nameof(NotIntersectedRectanglesTestCases.Data))] + public bool ReturnValue(Rectangle first, Rectangle second) + { + return new[] { first, second }.HasIntersectedRectangles() + && new[] { second, first }.HasIntersectedRectangles(); + } + + [Test] + public void ReturnFalse_ThenConainsOneElement() + { + new[] { new Rectangle(0, 0, 1, 1) }.HasIntersectedRectangles().Should().BeFalse(); + } + + [Test] + public void ReturnFalse_ThenEmpty() + { + Array.Empty().HasIntersectedRectangles().Should().BeFalse(); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/SpiralCloudShaperTests.cs b/cs/TagCloudTests/SpiralCloudShaperTests.cs new file mode 100644 index 000000000..0d7b1492a --- /dev/null +++ b/cs/TagCloudTests/SpiralCloudShaperTests.cs @@ -0,0 +1,44 @@ +using System.Drawing; +using System.Runtime.ExceptionServices; +using FluentAssertions; +using TagCloud; + +namespace TagCloudTests; + +public class SpiralCloudShaperTests +{ + private Point center; + private SpiralCloudShaper shaper; + + [SetUp] + public void SetUp() + { + center = new Point(0, 0); + shaper = SpiralCloudShaper.Create(center); + } + + [TestCase(-1, 1, TestName = "NegativeCoefficient")] + [TestCase(1, -1, TestName = "NegativeDeltaAngle")] + [TestCase(-1, -1, TestName = "NegativeDeltaAngleAndCoefficient")] + public void Throw_OnCreationWith(double coefficient, double deltaAngle) + { + Assert.Throws(() => SpiralCloudShaper.Create(center, coefficient, deltaAngle)); + } + + [TestCase(1, 1, TestName = "Integer coefficients")] + [TestCase(0.1D, 0.1D, TestName = "Double coefficients")] + public void NotThrow_OnCreationWith(double coefficient, double deltaAngle) + { + Assert.DoesNotThrow(() => SpiralCloudShaper.Create(center, coefficient, deltaAngle)); + } + + [Test] + public void RadiusShouldIncrease() + { + shaper.GetPossiblePoints() + .Take(100) + .Select(point => point.GetDistanceTo(center)) + .Should() + .BeInAscendingOrder(); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudDrawerTest.cs b/cs/TagCloudTests/TagCloudDrawerTest.cs new file mode 100644 index 000000000..d83333bb9 --- /dev/null +++ b/cs/TagCloudTests/TagCloudDrawerTest.cs @@ -0,0 +1,60 @@ +using System.Drawing; +using FluentAssertions; +using TagCloud; + +namespace TagCloudTests; + +public class TagCloudDrawerTest +{ + private const string RelativePathToTestDirectory = @"..\..\..\Test"; + + private static readonly string path = Path.GetFullPath(RelativePathToTestDirectory); + private readonly DirectoryInfo directory = new DirectoryInfo(path); + + private TagCloudDrawer drawer; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + foreach (var file in directory.EnumerateFiles()) + file.Delete(); + } + + [SetUp] + public void SetUp() + { + drawer = TagCloudDrawer.Create(path, new ConstantColorSelector(Color.Black)); + } + + [Test] + public void Throw_ThenDrawEmptyList() + { + Assert.Throws(() => drawer.Draw(Array.Empty(), "throw")); + } + + [Test] + public void Throw_ThenDirectoryDoesNotExist() + { + Assert.Throws(() => TagCloudDrawer.Create( + $@"{path}\DontExist", + new ConstantColorSelector(Color.Black)) + ); + } + + [Test] + public void DrawOneCase() + { + var rectangles = new Rectangle[] + { + new Rectangle(0, 1, 2, 3), + new Rectangle(4, 5, 6, 7), + new Rectangle(8, 9, 10, 11), + }; + drawer.Draw(rectangles, "three_rectangles"); + directory + .EnumerateFiles() + .Count(file => file.Name.Contains("three_rectangles")) + .Should() + .Be(1); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudTests.csproj b/cs/TagCloudTests/TagCloudTests.csproj new file mode 100644 index 000000000..61f237b8b --- /dev/null +++ b/cs/TagCloudTests/TagCloudTests.csproj @@ -0,0 +1,35 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs b/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs new file mode 100644 index 000000000..a96784f8b --- /dev/null +++ b/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Drawing; + +namespace TagCloudTests.TestData; + +public class IntersectedRectanglesTestCases +{ + public static IEnumerable Data + { + get + { + yield return new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) + .SetName("WhenFirstEqualsSecond").Returns(true); + yield return new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstInSecond").Returns(true); + yield return new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByLeftSide").Returns(true); + yield return new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByRightSide").Returns(true); + yield return new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByTopSide").Returns(true); + yield return new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByBottomSide").Returns(true); + yield return new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) + .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle").Returns(true); + yield return new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) + .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle").Returns(true); + } + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs b/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs new file mode 100644 index 000000000..c11563999 --- /dev/null +++ b/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Drawing; + +namespace TagCloudTests.TestData; + +public class NotIntersectedRectanglesTestCases +{ + public static IEnumerable Data + { + get + { + yield return new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) + .SetName("WhenFirstRightBorder_СoncidesWithSecondLeftBorder").Returns(false); + yield return new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(0, 1, 1, 1)) + .SetName("WhenFirstTopBorder_СoncidesWithSecondBottomBorder").Returns(false); + } + } +} \ No newline at end of file diff --git a/cs/tdd.sln b/cs/tdd.sln index c8f523d63..839ee2a35 100644 --- a/cs/tdd.sln +++ b/cs/tdd.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloud", "TagCloud\TagCloud.csproj", "{449E90CC-3D52-4E1C-8BB9-73C93CA79781}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloudTests", "TagCloudTests\TagCloudTests.csproj", "{C7C10229-7576-41A9-B0A0-2A9939985DB0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Debug|Any CPU.Build.0 = Debug|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Release|Any CPU.ActiveCfg = Release|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Release|Any CPU.Build.0 = Release|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE