Selaa lähdekoodia

Merge branch 'develop'

Martin Edlund 6 vuotta sitten
vanhempi
commit
448b2057b0

+ 13 - 0
Finished Userstories/userstory10.md

@@ -0,0 +1,13 @@
+# Userstory 10  
+ 
+|**ID**|10|  
+|-|-|
+|**Name**|Rückgängig Funktion|
+|**Beschreibung**|Man kann Aktionen wie das Zeichnen und Löschen von Linien rückgängig machen kann|
+|**Akzeptanzkriterium**|Wenn Elemente im Aktionsprotokoll vorhanden ist, kann man mit Strg+Z oder einen Knopf in der Toolbar, das letzte Ereignis rückgängig machen. D.h. das rechte Bild kehrt zu dem Zustand zurück an dem es vor dem rückgängig gemachten Ereignis war.|
+|Geschätzter Aufwand (Story Points)|2|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|3|
+|Tatsächlicher Aufwand (Std.)|1.5|
+|Velocity (Std./Story Point)|0.75|
+|Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory11.md

@@ -0,0 +1,13 @@
+# Userstory 11  
+ 
+|**ID**|11|  
+|-|-|
+|**Name**|Aktion wiederholen|
+|**Beschreibung**|Ereignisse die Rückgängig gemacht wurden, können widerhergestellt werden.|
+|**Akzeptanzkriterium**|Wenn Ereignisse im Aktionsprotokoll vorhanden sind, kann man mit Strg+y oder einem Knopf in der Toolbar das verworfene Ereignis wiederherstellen.|
+|Geschätzter Aufwand (Story Points)|2|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|3|
+|Tatsächlicher Aufwand (Std.)|1.5|
+|Velocity (Std./Story Point)|0.75|
+|Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory6.md

@@ -0,0 +1,13 @@
+# Userstory 6
+
+|**ID**|6|
+|-|-|
+|**Name**|Löschfunktionalität|
+|**Beschreibung**|Man kann einen Knopf in der UI drücken um in den Löschmodus zu kommen, und um wieder aus dem Löschmodus rauszukommen. Wenn man im Löschmodus ist, kann man auf einer Leinwand im rechten Bereich gezeichnete Elemente löschen indem man auf sie klickt.|
+|**Akzeptanzkriterium**|Der Knopf für den Löschmodus kann nur gedrückt werden, wenn im rechten Bereich eine Zeichennleinwand vorhanden ist. Wenn man den Knopf drückt ohne im Löschmodus zu sein, verändert sich der Knopf erkennbar und man kommt in den Löschmodus. Drückt man den Knopf, während man im Löschmodus ist, wird der Löschmodus beendet und der Knopf bekommt wieder sein urspüngliches Aussehen. Während man im Löschmodus ist, kann man auf der Leinwand im rechten Bereich Elemente löschen, wenn der Mauszeiger sie berührt während man die Maustaste gedrückt hält. Die Elemente die man löscht verschwinden. Drückt man den Löschknopf während man in einem anderen Modus ist (z.B. Zeichenmodus) wird der andere Modus beendet und man ist im Löschmodus.|
+|Geschätzter Aufwand (Story Points)|16|
+|Entwickler|Martin Edlund, Tim Reischl|
+|Umgesetzt in Iteration|3|
+|Tatsächlicher Aufwand (Std.)|14,5|
+|Velocity (Std./Story Point)|0,90625|
+|Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory7.md

@@ -0,0 +1,13 @@
+# Userstory 7
+
+|**ID**|7|
+|-|-|
+|**Name**|Aktionsprotokoll|
+|**Beschreibung**|Bei jeder Aktion im Zusammenhang mit der Leinwand wird dies in einem Protokoll gespeichert, welches man sich anzeigen kann. Dies soll spätere Funktionalität ermöglichen, bei der man Aktionen rückgängig machen kann. Dieses Protokoll kann sich der Nutzer als Liste anzeigen lassen. |
+|**Akzeptanzkriterium**|Wenn eine Aktion im Bezug zur Leinwand durchgeführt wird, wird dass in einem Protokoll gespeichert. Diese Aktionen wären: Eine Neue Leinwand zu erstellen und verschiedene Interaktionen mit der Leinwand in verschiedenen Modi (Zeichenmodus, Löschmodus, etc.). Es wird die chronologische Reinfolge der Aktionen, die Änderungen die durch diese Aktion stattfand, eine kurze Beschreibung der Aktion, sowie die Art der Aktion gespeichert. Wenn das Programm geschlossen wird, verfällt das Aktionsprotokoll. Jedes mal wenn eine Änderung stattfindet, wird die letzte Aktion im Fenster angezeigt.|
+|Geschätzter Aufwand (Story Points)|20|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|3|
+|Tatsächlicher Aufwand (Std.)|5|
+|Velocity (Std./Story Point)|0,25|
+|Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory9.md

@@ -0,0 +1,13 @@
+# Userstory 9  
+ 
+|**ID**|9|  
+|-|-|
+|**Name**|Funktionalität im Großformat|
+|**Beschreibung**|Inhalte des Programs funktionieren auf der Videowall wie auf einem Laptop.|
+|**Akzeptanzkriterium**|Alle bereits umgesetzten Userstories / Features funktioneren auf der Videowall des Fachbereiches TK genau wie auf einem kleineren Display. Insbesondere heißt das, dass Bilder korrekt skaliert werden, alle Interaktionspunkte mit dem Programm die gewollten Ergebnisse liefern, etc.|
+|Geschätzter Aufwand (Story Points)|1|
+|Entwickler|Martin Edlund, Tim Reischl|
+|Umgesetzt in Iteration|2|
+|Tatsächlicher Aufwand (Std.)|1|
+|Velocity (Std./Story Point)|1|
+|Bemerkungen|Keine|

+ 20 - 0
SketchAssistant/SketchAssistant.Tests/Properties/AssemblyInfo.cs

@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("SketchAssistant.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SketchAssistant.Tests")]
+[assembly: AssemblyCopyright("Copyright ©  2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("7dcdc31a-8291-4b05-93d6-dcc5de27a4a0")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 80 - 0
SketchAssistant/SketchAssistant.Tests/SketchAssistant.Tests.csproj

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{7DCDC31A-8291-4B05-93D6-DCC5DE27A4A0}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>SketchAssistant.Tests</RootNamespace>
+    <AssemblyName>SketchAssistant.Tests</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
+    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
+    <IsCodedUITest>False</IsCodedUITest>
+    <TestProjectType>UnitTest</TestProjectType>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Windows.Forms" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="UnitTest1.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="app.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SketchAssistant\SketchAssistant.csproj">
+      <Project>{0336f628-a2f7-4170-8b2e-9277c23118d4}</Project>
+      <Name>SketchAssistant</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.props'))" />
+    <Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets'))" />
+  </Target>
+  <Import Project="..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.1.4.0\build\net45\MSTest.TestAdapter.targets')" />
+</Project>

+ 373 - 0
SketchAssistant/SketchAssistant.Tests/UnitTest1.cs

@@ -0,0 +1,373 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Drawing;
+using System.Collections.Generic;
+using SketchAssistant;
+using System.Windows.Forms;
+
+namespace Tests
+{
+    [TestClass]
+    public class LineTests
+    {
+        //========================//
+        //= Bresenham Line Tests =//
+        //========================//
+
+        [TestMethod]
+        public void BresenhamLineTest1()
+        {
+            //Test point
+            List<Point> expectedResult = new List<Point>();
+            expectedResult.Add(new Point(1, 2));
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(1, 2), new Point(1, 2));
+            Assert.AreEqual(1, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        [TestMethod]
+        public void BresenhamLineTest2()
+        {
+            //Test line going from left to right
+            List<Point> expectedResult = new List<Point>();
+            for (int i = 1; i <= 6; i++) { expectedResult.Add(new Point(i, 2)); }
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(1, 2), new Point(6, 2));
+            Assert.AreEqual(expectedResult.Count, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        [TestMethod]
+        public void BresenhamLineTest3()
+        {
+            //Test line going from right to left
+            List<Point> expectedResult = new List<Point>();
+            for (int i = 6; i >= 1; i--) { expectedResult.Add(new Point(i, 2)); }
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(6, 2), new Point(1, 2));
+            Assert.AreEqual(expectedResult.Count, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        [TestMethod]
+        public void BresenhamLineTest4()
+        {
+            //Test line going from top to bottom
+            List<Point> expectedResult = new List<Point>();
+            for (int i = 5; i <= 25; i++) { expectedResult.Add(new Point(7, i)); }
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(7, 5), new Point(7, 25));
+            Assert.AreEqual(expectedResult.Count, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        [TestMethod]
+        public void BresenhamLineTest5()
+        {
+            //Test line going from bottom to top
+            List<Point> expectedResult = new List<Point>();
+            for (int i = 25; i >= 5; i--) { expectedResult.Add(new Point(7, i)); }
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(7, 25), new Point(7, 5));
+            Assert.AreEqual(expectedResult.Count, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        [TestMethod]
+        public void BresenhamLineTest6()
+        {
+            //Test exactly diagonal line from top left to bottom right
+            List<Point> expectedResult = new List<Point>();
+            for (int i = 5; i <= 25; i++) { expectedResult.Add(new Point(i + 2, i)); }
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(7, 5), new Point(27, 25));
+            Assert.AreEqual(expectedResult.Count, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        [TestMethod]
+        public void BresenhamLineTest7()
+        {
+            //Test exactly diagonal line from bottom right to top left
+            List<Point> expectedResult = new List<Point>();
+            for (int i = 25; i >= 5; i--) { expectedResult.Add(new Point(i + 2, i)); }
+            List<Point> actualResult = SketchAssistant.Line.BresenhamLineAlgorithm(new Point(27, 25), new Point(7, 5));
+            Assert.AreEqual(expectedResult.Count, actualResult.Count);
+            for (int i = 0; i < actualResult.Count; i++)
+            {
+                Assert.AreEqual(expectedResult[i], actualResult[i]);
+            }
+        }
+
+        //===========================//
+        //= Matrix Population Tests =//
+        //===========================//
+
+        [TestMethod]
+        public void MatrixTest1()
+        {
+            //Populate Matrix for temporary Line
+            List<Point> thePoints = new List<Point>();
+            thePoints.Add(new Point(1, 1));
+            thePoints.Add(new Point(1, 2));
+            bool[,] testBoolMatrix = new bool[5, 5];
+            List<int>[,] testLineMatrix = new List<int>[5, 5];
+            bool[,] resultBoolMatrix = new bool[5, 5];
+            HashSet<int>[,] resultLineMatrix = new HashSet<int>[5, 5];
+            Line testLine = new Line(thePoints);
+            testLine.PopulateMatrixes(resultBoolMatrix, resultLineMatrix);
+            for (int i = 0; i < 5; i++)
+            {
+                for (int j = 0; j < 5; j++)
+                {
+                    Assert.AreEqual(testBoolMatrix[i, j], resultBoolMatrix[i, j]);
+                    Assert.AreEqual(testLineMatrix[i, j], resultLineMatrix[i, j]);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void MatrixTest2()
+        {
+            //Populate Matrix for non-temporary Line
+            List<Point> thePoints = new List<Point>();
+            thePoints.Add(new Point(1, 1));
+            thePoints.Add(new Point(3, 3));
+            bool[,] testBoolMatrix = new bool[5, 5];
+            HashSet<int>[,] testLineMatrix = new HashSet<int>[5, 5];
+            for (int i = 1; i <= 3; i++)
+            {
+                testBoolMatrix[i, i] = true;
+                HashSet<int> temp = new HashSet<int>();
+                temp.Add(5);
+                testLineMatrix[i, i] = temp;
+            }
+            bool[,] resultBoolMatrix = new bool[5, 5];
+            HashSet<int>[,] resultLineMatrix = new HashSet<int>[5, 5];
+            Line testLine = new Line(thePoints, 5);
+            testLine.PopulateMatrixes(resultBoolMatrix, resultLineMatrix);
+            for (int i = 0; i < 5; i++)
+            {
+                for (int j = 0; j < 5; j++)
+                {
+                    Assert.AreEqual(testBoolMatrix[i, j], resultBoolMatrix[i, j]);
+                    if (testLineMatrix[i, j] != null && resultLineMatrix[i, j] != null)
+                    {
+                        for (int k = 0; k < resultLineMatrix[i, j].Count; k++)
+                        {
+                            Assert.AreEqual(true, testLineMatrix[i, j].SetEquals(resultLineMatrix[i, j]));
+                        }
+                    }
+                }
+            }
+        }
+
+        //=========================//
+        //= Line Constructor Test =//
+        //=========================//
+
+        [TestMethod]
+        public void ConstructorTest()
+        {
+            //Create non-temporary Line and check points
+            //reference Points
+            List<Point> comparisonPoints = new List<Point> {new Point(2,2), new Point(3, 1), new Point(4, 1), new Point(5, 1), new Point(6, 2),
+                new Point(7, 3), new Point(8, 4), new Point(9, 5), new Point(10, 6), new Point(11, 5), new Point(11, 4), new Point(11, 3),
+                new Point(10, 2), new Point(9, 1), new Point(8, 2), new Point(7, 3), new Point(6, 4), new Point(5, 5), new Point(4, 5),
+                new Point(3, 5), new Point(2, 5), new Point(1, 4)};
+            //test Points, with intermediate points missing & duplicate points
+            List<Point> testPoints = new List<Point> {new Point(2,2), new Point(3, 1), new Point(5, 1), new Point(5, 1), new Point(5, 1),
+                new Point(8, 4), new Point(10, 6), new Point(11, 5), new Point(11, 3), new Point(9, 1), new Point(9, 1), new Point(9, 1),
+                new Point(5, 5), new Point(2, 5), new Point(2, 5), new Point(1, 4) };
+            Line testLine = new Line(testPoints, 0);
+            List<Point> returnedPoints = testLine.GetPoints();
+            Assert.AreEqual(comparisonPoints.Count, returnedPoints.Count);
+            for (int i = 0; i < returnedPoints.Count; i++)
+            {
+                Assert.AreEqual(comparisonPoints[i], returnedPoints[i]);
+            }
+        }
+    }
+
+    [TestClass]
+    public class ActionHistoryTests
+    {
+        ToolStripStatusLabel testLabel = new ToolStripStatusLabel();
+
+        private ActionHistory GetActionHistory()
+        {
+            return new ActionHistory(testLabel);
+        }
+
+        [DataTestMethod]
+        [DataRow(SketchAction.ActionType.Start, 5, -1, "A new canvas was created.")]
+        [DataRow(SketchAction.ActionType.Draw, 5, 5, "Line number 5 was drawn.")]
+        [DataRow(SketchAction.ActionType.Delete, 10, 10, "Line number 10 was deleted.")]
+        public void ScetchActionTest1(SketchAction.ActionType type, int id, int exit, String response)
+        {
+            HashSet<int> actualResult = new HashSet<int>();
+            if (!type.Equals(SketchAction.ActionType.Start)) { actualResult.Add(id); }
+            SketchAction testAction = new SketchAction(type, id);
+            Assert.AreEqual(type, testAction.GetActionType());
+            Assert.AreEqual(true, actualResult.SetEquals(testAction.GetLineIDs()));
+            Assert.AreEqual(response, testAction.GetActionInformation());
+        }
+
+        [DataTestMethod]
+        [DataRow(SketchAction.ActionType.Start, 1, 2, 3, "A new canvas was created.")]
+        [DataRow(SketchAction.ActionType.Draw, 3, 3, 3, "Line number 3 was drawn.")]
+        [DataRow(SketchAction.ActionType.Delete, 20, 30, 40, "Several Lines were deleted.")]
+        public void ScetchActionTest2(SketchAction.ActionType type, int id1, int id2, int id3, String response)
+        {
+            HashSet<int> actualResult = new HashSet<int>();
+            if (!type.Equals(SketchAction.ActionType.Start))
+            {
+                actualResult.Add(id1);
+                actualResult.Add(id2);
+                actualResult.Add(id3);
+            }
+            SketchAction testAction = new SketchAction(type, actualResult);
+            Assert.AreEqual(type, testAction.GetActionType());
+            Assert.AreEqual(true, actualResult.SetEquals(testAction.GetLineIDs()));
+            Assert.AreEqual(response, testAction.GetActionInformation());
+        }
+
+        [DataTestMethod]
+        [DataRow(SketchAction.ActionType.Start, SketchAction.ActionType.Start, true)]
+        [DataRow(SketchAction.ActionType.Draw, SketchAction.ActionType.Delete, false)]
+        public void ActionHistoryTest1(SketchAction.ActionType action1, SketchAction.ActionType action2, bool isEmpty)
+        {
+            ActionHistory testHistory = GetActionHistory();
+            if (!action1.Equals(SketchAction.ActionType.Start)) { testHistory.AddNewAction(new SketchAction(action1, 5)); }
+            if (!action2.Equals(SketchAction.ActionType.Start)) { testHistory.AddNewAction(new SketchAction(action2, 5)); }
+            Assert.AreEqual(isEmpty, testHistory.IsEmpty());
+        }
+
+        [DataTestMethod]
+        [DataRow(SketchAction.ActionType.Draw, "Last Action: Line number 0 was drawn.")]
+        [DataRow(SketchAction.ActionType.Delete, "Last Action: Line number 0 was deleted.")]
+        public void ActionHistoryUndoRedoTest(SketchAction.ActionType actionType, String message)
+        {
+            ActionHistory testHistory = GetActionHistory();
+            SketchAction testAction = new SketchAction(actionType, 0);
+            testHistory.AddNewAction(testAction);
+            Assert.AreEqual(true, testHistory.CanUndo());
+            testHistory.MoveAction(true);
+            Assert.AreEqual(true, testHistory.CanRedo());
+            testHistory.MoveAction(false);
+            Assert.AreEqual(actionType, testHistory.GetCurrentAction().GetActionType());
+            String currLabel = testLabel.Text;
+            Assert.AreEqual(currLabel, message);
+        }
+    }
+
+    [TestClass]
+    public class FileImporterTests
+    {
+        [DataTestMethod]
+        [DataRow(new int[] { 54, 43, 57, 11, 145, 34, 113, 299, 0 }, new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 })]
+        [DataRow(new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 }, new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 })]
+        [DataRow(new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 }, new int[] { 54, 43, 57, 11, 145, 34, 113, 199, 0 })]
+        public void ParseISADInputSuccessfulTest(int[] xCoordinates, int[] yCoordinates)
+        {
+            Form1 program = new Form1();
+            FileImporter uut = new SketchAssistant.FileImporter(program);
+
+            List<String> file = new List<string>();
+            file.Add("drawing");
+            file.Add("300x200");
+            for (int i = 0; i < xCoordinates.Length - 2; i += 3)
+            {
+                file.Add("line");
+                file.Add(xCoordinates[i] + ";" + yCoordinates[i]);
+                file.Add(xCoordinates[i + 1] + ";" + yCoordinates[i + 1]);
+                file.Add(xCoordinates[i + 2] + ";" + yCoordinates[i + 2]);
+                file.Add("endline");
+            }
+            file.Add("enddrawing");
+
+            (int, int, List<Line>) values = uut.ParseISADInputForTesting(file.ToArray());
+            program.CreateCanvasAndSetPictureForTesting(values.Item1, values.Item2, values.Item3);
+
+            Line[] drawing = GetLeftImage(program).ToArray();
+
+            Assert.AreEqual(xCoordinates.Length / 3, drawing.Length);
+            for (int i = 0; i < xCoordinates.Length - 2; i += 3)
+            {
+                Point[] currentLine = drawing[i / 3].GetPoints().ToArray();
+                Assert.AreEqual(3, currentLine.Length);
+                for (int j = 0; j < 3; j++)
+                {
+                    Assert.IsTrue(currentLine[j].X == xCoordinates[i + j] && currentLine[j].Y == yCoordinates[i + j]);
+                }
+            }
+        }
+
+        [DataTestMethod]
+        [DataRow(new String[] {})]
+        [DataRow(new String[] { "begindrawing", "300x300", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300;300", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "30.5x300", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "line", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "beginline", "50;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "500;50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50x50", "100;50", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50", "100", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "line", "endline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "stopline", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "enddrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "endline", "endrawing" })]
+        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "endline" })]
+        public void ParseISADInputExceptionTest(String[] file)
+        {
+            bool exceptionThrown = false;
+            Form1 program = new Form1();
+            FileImporter uut = new SketchAssistant.FileImporter(program);
+            //check that left image initially is uninitialized
+            Assert.IsNull(GetLeftImage(program));
+            //initialize left image with a valid isad drawing
+            (int, int, List<Line>) values = uut.ParseISADInputForTesting(new string[] { "drawing", "300x205", "line", "40;40", "140;140", "endline", "enddrawing" });
+            program.CreateCanvasAndSetPictureForTesting(values.Item1, values.Item2, values.Item3);
+            //save left image for later comparison
+            List<Line> oldLeftImage = GetLeftImage(program);
+            try
+            {
+                //try to initialize the left image with an invalid isad drawing
+                (int, int, List<Line>) values1 = uut.ParseISADInputForTesting(file);
+                program.CreateCanvasAndSetPictureForTesting(values1.Item1, values1.Item2, values1.Item3);
+            }
+            catch(FileImporterException)
+            {
+                //save the occurence of an exception
+                exceptionThrown = true;
+            }
+            //check that an exception has been thrown
+            Assert.IsTrue(exceptionThrown);
+            //check that the left image has not been changed by the failed image import
+            Assert.AreEqual(oldLeftImage, GetLeftImage(program));
+        }
+
+        /// <summary>
+        /// local helper method retrieving the left image from a Form1 instance
+        /// </summary>
+        /// <returns>the left image of the given Form1 instance</returns>
+        private List<Line> GetLeftImage(Form1 program)
+        {
+            //cast is save as long as Form1#GetAllVariables() is conform to its contract
+            return (List<Line>) program.GetAllVariables()[4];
+        }
+    }
+}

+ 11 - 0
SketchAssistant/SketchAssistant.Tests/app.config

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.4.1" newVersion="4.0.4.1" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 6 - 0
SketchAssistant/SketchAssistant.Tests/packages.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="MSTest.TestAdapter" version="1.4.0" targetFramework="net461" />
+  <package id="MSTest.TestFramework" version="1.4.0" targetFramework="net461" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" />
+</packages>

+ 6 - 0
SketchAssistant/SketchAssistant.sln

@@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.28010.2050
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SketchAssistant", "SketchAssistant\SketchAssistant.csproj", "{0336F628-A2F7-4170-8B2E-9277C23118D4}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SketchAssistant.Tests", "SketchAssistant.Tests\SketchAssistant.Tests.csproj", "{7DCDC31A-8291-4B05-93D6-DCC5DE27A4A0}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
 		{0336F628-A2F7-4170-8B2E-9277C23118D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{0336F628-A2F7-4170-8B2E-9277C23118D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{0336F628-A2F7-4170-8B2E-9277C23118D4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7DCDC31A-8291-4B05-93D6-DCC5DE27A4A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7DCDC31A-8291-4B05-93D6-DCC5DE27A4A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7DCDC31A-8291-4B05-93D6-DCC5DE27A4A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7DCDC31A-8291-4B05-93D6-DCC5DE27A4A0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 110 - 0
SketchAssistant/SketchAssistant/ActionHistory.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace SketchAssistant
+{
+    public class ActionHistory
+    {
+        //History of Actions taken
+        List<SketchAction> actionHistory;
+        //The current position in the actionHistory
+        Tuple<int, SketchAction> currentAction;
+        //The label in which the current action is displayed
+        ToolStripStatusLabel displayLabel;
+
+        public ActionHistory(ToolStripStatusLabel displayPosition)
+        {
+            displayLabel = displayPosition;
+            actionHistory = new List<SketchAction>();
+            currentAction = new Tuple<int, SketchAction>(-1, null);
+            AddNewAction(new SketchAction(SketchAction.ActionType.Start, -1));
+        }
+
+        /// <summary>
+        /// Adds a new action to the action history.
+        /// </summary>
+        /// <param name="newAction">The newly added action.</param>
+        public void AddNewAction(SketchAction newAction)
+        {
+            //The current Action is before the last action taken, delete everything after the current action.
+            if (currentAction.Item1 < actionHistory.Count - 1)
+            {
+                actionHistory.RemoveRange(currentAction.Item1 + 1, actionHistory.Count - (currentAction.Item1 + 1));
+            }
+            actionHistory.Add(newAction);
+            currentAction = new Tuple<int, SketchAction>(actionHistory.Count - 1, newAction);
+            UpdateStatusLabel();
+        }
+
+        /// <summary>
+        /// Changes the currentAction.
+        /// </summary>
+        /// <param name="moveBack">If True, moves the current action back one slot, if False, moves it forward.</param>
+        public void MoveAction(bool moveBack)
+        {
+            if(moveBack && CanUndo())
+            {
+                currentAction = new Tuple<int, SketchAction>(currentAction.Item1 - 1, actionHistory[currentAction.Item1 - 1]);
+            }
+            if(!moveBack && CanRedo())
+            {
+                currentAction = new Tuple<int, SketchAction>(currentAction.Item1 + 1, actionHistory[currentAction.Item1 + 1]);
+            }
+            UpdateStatusLabel();
+        }
+
+        /// <summary>
+        /// Returns the current action.
+        /// </summary>
+        /// <returns>The current action.</returns>
+        public SketchAction GetCurrentAction()
+        {
+            return currentAction.Item2;
+        }
+
+        /// <summary>
+        /// Return whether or not an action can be undone.
+        /// </summary>
+        /// <returns>True if an action can be undone.</returns>
+        public bool CanUndo()
+        {
+            if (currentAction.Item1 > 0) { return true; }
+            else { return false; }
+        }
+
+        /// <summary>
+        /// Return whether or not an action can be redone.
+        /// </summary>
+        /// <returns>True if an action can be redone.</returns>
+        public bool CanRedo()
+        {
+            if (currentAction.Item1 < actionHistory.Count - 1) { return true; }
+            else { return false; }
+        }
+
+        /// <summary>
+        /// Returns whether or not the history is empty.
+        /// </summary>
+        /// <returns>true if the history is empty, otherwise false</returns>
+        public bool IsEmpty()
+        {
+            if (actionHistory.Count == 1) { return true; }
+            else { return false; }
+        }
+
+        /// <summary>
+        /// Updates the status label if there is one given.
+        /// </summary>
+        private void UpdateStatusLabel()
+        {
+            if (displayLabel != null)
+            {
+                displayLabel.Text = "Last Action: " + currentAction.Item2.GetActionInformation();
+            }
+        }
+    }
+}

+ 9 - 1
SketchAssistant/SketchAssistant/App.config

@@ -1,6 +1,14 @@
-<?xml version="1.0" encoding="utf-8" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup> 
         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
     </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.4.1" newVersion="4.0.4.1" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
 </configuration>

+ 155 - 0
SketchAssistant/SketchAssistant/FileImporter.cs

@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace SketchAssistant
+{
+    public class FileImporter
+    {
+
+        /// <summary>
+        /// pointer to the running instance of main program
+        /// </summary>
+        Form1 program;
+
+        public FileImporter(Form1 newProgram)
+        {
+            program = newProgram;
+        }
+
+        /// <summary>
+        /// parses a drawing consisting of line objects, given as a file in the application specific .isad format
+        /// </summary>
+        /// <param name="fileName">the path of the input file</param>
+        /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
+        public (int, int, List<Line>) ParseISADInputFile(String fileName)
+        {
+            return ParseISADInput(System.IO.File.ReadAllLines(fileName));
+        }
+
+        /// <summary>
+        /// parses a drawing consisting of line objects, given as the content of a .isad file, seperated into lines
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
+        private (int, int, List<Line>) ParseISADInput(String[] allLines)
+        {
+
+            if (allLines.Length == 0)
+            {
+                throw new FileImporterException("file is empty", "", -1);
+            }
+            if (!"drawing".Equals(allLines[0]))
+            {
+                throw new FileImporterException("file is not an interactive sketch assistant drawing", ".isad files have to start with the 'drawing' token", 1);
+            }
+            if (!"enddrawing".Equals(allLines[allLines.Length - 1]))
+            {
+                throw new FileImporterException("unterminated drawing definition", ".isad files have to end with the 'enddrawing' token", allLines.Length);
+            }
+
+            (int, int) dimensions = ParseISADHeader(allLines);
+            List<Line> picture = ParseISADBody(allLines, dimensions.Item1, dimensions.Item2);
+
+            return (dimensions.Item1, dimensions.Item2, picture);
+        }
+
+
+
+        /// <summary>
+        /// parses the first two lines of an input file in .isad format
+        /// </summary>
+        /// <param name="allLines">the input file as an array of lines</param>
+        /// <returns>the width and height of the left canvas</returns>
+        private (int, int) ParseISADHeader(String[] allLines)
+        {
+            int width;
+            int height;
+            if (!(allLines.Length > 1) || !Regex.Match(allLines[1], @"^\d+x?\d+$", RegexOptions.None).Success)
+            {
+                throw new FileImporterException("invalid or missing canvas size definition", "format: [width]x[heigth]", 2);
+            }
+            String[] size = allLines[1].Split('x');
+            width = Convert.ToInt32(size[0]);
+            height = Convert.ToInt32(size[1]);
+            return (width, height);
+        }
+
+        /// <summary>
+        /// parses all line entries of an input file in .isad format
+        /// </summary>
+        /// <param name="allLines">the input file as an array of lines</param>
+        /// <returns>the parsed picture as a list of lines</returns>
+        private List<Line> ParseISADBody(String[] allLines, int width, int height)
+        {
+
+            String lineStartString = "line";
+            String lineEndString = "endline";
+
+            List<Line> drawing = new List<Line>();
+
+            //number of the line currently being parsed, enumeration starting at 0, body starts at the third line, therefore lin number 2
+            int i = 2;
+            //parse 'line' token and complete line definition
+            int lineStartPointer = i;
+            //holds the line number of the next expected beginning of a line definition, or of the enddrawing token
+            while (lineStartString.Equals(allLines[i]))
+            {
+                //start parsing next line
+                i++;
+                List<Point> newLine = new List<Point>();
+                while (!lineEndString.Equals(allLines[i]))
+                {
+                    if (i == allLines.Length)
+                    {
+                        throw new FileImporterException("unterminated line definition", null, (i + 1));
+                    }
+                    //parse single point definition
+                    if (!Regex.Match(allLines[i], @"^\d+;\d+$", RegexOptions.None).Success)
+                    {
+                        throw new FileImporterException("invalid Point definition: wrong format", "format: [xCoordinate];[yCoordinate]", (i + 1) );
+                    }
+                    String[] coordinates = allLines[i].Split(';');
+                    //no errors possible, convertability to int already checked above
+                    int xCoordinate = Convert.ToInt32(coordinates[0]);
+                    int yCoordinate = Convert.ToInt32(coordinates[1]);
+                    if (xCoordinate < 0 || yCoordinate < 0 || xCoordinate > width - 1 || yCoordinate > height - 1)
+                    {
+                        throw new FileImporterException("invalid Point definition: point out of bounds", null, (i + 1) );
+                    }
+                    newLine.Add(new Point(xCoordinate, yCoordinate));
+                    //start parsing next line
+                    i++;
+                }
+                //"parse" 'endline' token, syntax already checked at the beginning,  and start parsing next line
+                i++;
+                //add line to drawing
+                drawing.Add(new Line(newLine));
+                //update lineStartPointer to the presumable start of the next line
+                lineStartPointer = i;
+            }
+            //check if end of body is reached after there are no more line definitions
+            if(i != allLines.Length - 1)
+            {
+                throw new FileImporterException("missing or invalid line definition token", "line definitions start with the 'line' token", (i + 1));
+            }
+            //return parsed picture
+            return drawing;
+        }
+
+        /// <summary>
+        /// connection point for testing use only: calls ParseISADInput(String[] allLines) and directly passes the given argument (effectively bypassing the File Input functionality)
+        /// </summary>
+        /// <param name="allLines">an array holding all lines of the input file</param>
+        /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
+        public (int, int, List<Line>) ParseISADInputForTesting(String[] allLines)
+        {
+            return ParseISADInput(allLines);
+        }
+
+    }
+}

+ 26 - 0
SketchAssistant/SketchAssistant/FileImporterException.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SketchAssistant
+{
+    public class FileImporterException : Exception
+    {
+        /// <summary>
+        /// the clean and formatted message to show to the user
+        /// </summary>
+        String showMessage;
+
+        public FileImporterException(String message, String hint, int lineNumber) : base (message)
+        {
+            showMessage = "Could not import file:\n\n" + message + (hint == null ? "" : "\n(Hint: " + hint + ")") + (lineNumber == -1 ? "" : "\n\n-line: " + lineNumber );
+        }
+
+        public override string ToString()
+        {
+            return showMessage;
+        }
+    }
+}

+ 83 - 11
SketchAssistant/SketchAssistant/Form1.Designer.cs

@@ -37,13 +37,19 @@ namespace SketchAssistant
             this.menuStrip1 = new System.Windows.Forms.MenuStrip();
             this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
             this.loadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.examplePictureToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
             this.toolStrip1 = new System.Windows.Forms.ToolStrip();
             this.canvasButton = new System.Windows.Forms.ToolStripButton();
             this.drawButton = new System.Windows.Forms.ToolStripButton();
+            this.deleteButton = new System.Windows.Forms.ToolStripButton();
             this.statusStrip1 = new System.Windows.Forms.StatusStrip();
             this.toolStripLoadStatus = new System.Windows.Forms.ToolStripStatusLabel();
             this.backgroundWorker2 = new System.ComponentModel.BackgroundWorker();
-            this.drawTimer = new System.Windows.Forms.Timer(this.components);
+            this.mouseTimer = new System.Windows.Forms.Timer(this.components);
+            this.lastActionTakenLabel = new System.Windows.Forms.ToolStripStatusLabel();
+            this.undoButton = new System.Windows.Forms.ToolStripButton();
+            this.redoButton = new System.Windows.Forms.ToolStripButton();
             this.tableLayoutPanel1.SuspendLayout();
             ((System.ComponentModel.ISupportInitialize)(this.pictureBoxRight)).BeginInit();
             ((System.ComponentModel.ISupportInitialize)(this.pictureBoxLeft)).BeginInit();
@@ -111,7 +117,8 @@ namespace SketchAssistant
             // fileToolStripMenuItem
             // 
             this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            this.loadToolStripMenuItem});
+            this.loadToolStripMenuItem,
+            this.importToolStripMenuItem});
             this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
             this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
             this.fileToolStripMenuItem.Text = "File";
@@ -123,11 +130,29 @@ namespace SketchAssistant
             this.loadToolStripMenuItem.Text = "Load...";
             this.loadToolStripMenuItem.Click += new System.EventHandler(this.loadToolStripMenuItem_Click);
             // 
+            // importToolStripMenuItem
+            // 
+            this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.examplePictureToolStripMenuItem});
+            this.importToolStripMenuItem.Name = "importToolStripMenuItem";
+            this.importToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
+            this.importToolStripMenuItem.Text = "Import...";
+            // 
+            // examplePictureToolStripMenuItem
+            // 
+            this.examplePictureToolStripMenuItem.Name = "examplePictureToolStripMenuItem";
+            this.examplePictureToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
+            this.examplePictureToolStripMenuItem.Text = "Example picture";
+            this.examplePictureToolStripMenuItem.Click += new System.EventHandler(this.examplePictureToolStripMenuItem_Click);
+            // 
             // toolStrip1
             // 
             this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.canvasButton,
-            this.drawButton});
+            this.drawButton,
+            this.deleteButton,
+            this.undoButton,
+            this.redoButton});
             this.toolStrip1.Location = new System.Drawing.Point(0, 24);
             this.toolStrip1.Name = "toolStrip1";
             this.toolStrip1.Size = new System.Drawing.Size(696, 25);
@@ -154,26 +179,65 @@ namespace SketchAssistant
             this.drawButton.Text = "Draw";
             this.drawButton.Click += new System.EventHandler(this.drawButton_Click);
             // 
+            // deleteButton
+            // 
+            this.deleteButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+            this.deleteButton.Image = ((System.Drawing.Image)(resources.GetObject("deleteButton.Image")));
+            this.deleteButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+            this.deleteButton.Name = "deleteButton";
+            this.deleteButton.Size = new System.Drawing.Size(44, 22);
+            this.deleteButton.Text = "Delete";
+            this.deleteButton.Click += new System.EventHandler(this.deleteButton_Click);
+            // 
             // statusStrip1
             // 
             this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            this.toolStripLoadStatus});
-            this.statusStrip1.Location = new System.Drawing.Point(0, 493);
+            this.toolStripLoadStatus,
+            this.lastActionTakenLabel});
+            this.statusStrip1.Location = new System.Drawing.Point(0, 491);
             this.statusStrip1.Name = "statusStrip1";
-            this.statusStrip1.Size = new System.Drawing.Size(696, 22);
+            this.statusStrip1.Size = new System.Drawing.Size(696, 24);
             this.statusStrip1.TabIndex = 4;
             this.statusStrip1.Text = "statusStrip1";
             // 
             // toolStripLoadStatus
             // 
             this.toolStripLoadStatus.Name = "toolStripLoadStatus";
-            this.toolStripLoadStatus.Size = new System.Drawing.Size(40, 17);
+            this.toolStripLoadStatus.Size = new System.Drawing.Size(40, 19);
             this.toolStripLoadStatus.Text = "no file";
             // 
-            // drawTimer
+            // mouseTimer
+            // 
+            this.mouseTimer.Interval = 1;
+            this.mouseTimer.Tick += new System.EventHandler(this.mouseTimer_Tick);
+            // 
+            // lastActionTakenLabel
+            // 
+            this.lastActionTakenLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left;
+            this.lastActionTakenLabel.Name = "lastActionTakenLabel";
+            this.lastActionTakenLabel.Size = new System.Drawing.Size(38, 19);
+            this.lastActionTakenLabel.Text = "none";
+            this.lastActionTakenLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+            // 
+            // undoButton
+            // 
+            this.undoButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+            this.undoButton.Image = ((System.Drawing.Image)(resources.GetObject("undoButton.Image")));
+            this.undoButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+            this.undoButton.Name = "undoButton";
+            this.undoButton.Size = new System.Drawing.Size(40, 22);
+            this.undoButton.Text = "Undo";
+            this.undoButton.Click += new System.EventHandler(this.undoButton_Click);
+            // 
+            // redoButton
             // 
-            this.drawTimer.Interval = 2;
-            this.drawTimer.Tick += new System.EventHandler(this.drawTimer_Tick);
+            this.redoButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+            this.redoButton.Image = ((System.Drawing.Image)(resources.GetObject("redoButton.Image")));
+            this.redoButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+            this.redoButton.Name = "redoButton";
+            this.redoButton.Size = new System.Drawing.Size(38, 22);
+            this.redoButton.Text = "Redo";
+            this.redoButton.Click += new System.EventHandler(this.redoButton_Click);
             // 
             // Form1
             // 
@@ -185,6 +249,7 @@ namespace SketchAssistant
             this.Controls.Add(this.toolStrip1);
             this.Controls.Add(this.tableLayoutPanel1);
             this.Controls.Add(this.menuStrip1);
+            this.KeyPreview = true;
             this.MainMenuStrip = this.menuStrip1;
             this.Margin = new System.Windows.Forms.Padding(2);
             this.Name = "Form1";
@@ -193,6 +258,7 @@ namespace SketchAssistant
             this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
             this.Load += new System.EventHandler(this.Form1_Load);
             this.SizeChanged += new System.EventHandler(this.Form1_Resize);
+            this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
             this.tableLayoutPanel1.ResumeLayout(false);
             ((System.ComponentModel.ISupportInitialize)(this.pictureBoxRight)).EndInit();
             ((System.ComponentModel.ISupportInitialize)(this.pictureBoxLeft)).EndInit();
@@ -219,9 +285,15 @@ namespace SketchAssistant
         private System.ComponentModel.BackgroundWorker backgroundWorker2;
         private System.Windows.Forms.PictureBox pictureBoxRight;
         private System.Windows.Forms.PictureBox pictureBoxLeft;
-        private System.Windows.Forms.Timer drawTimer;
+        private System.Windows.Forms.Timer mouseTimer;
         private System.Windows.Forms.ToolStripButton canvasButton;
         private System.Windows.Forms.ToolStripButton drawButton;
+        private System.Windows.Forms.ToolStripButton deleteButton;
+        private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem examplePictureToolStripMenuItem;
+        private System.Windows.Forms.ToolStripStatusLabel lastActionTakenLabel;
+        private System.Windows.Forms.ToolStripButton undoButton;
+        private System.Windows.Forms.ToolStripButton redoButton;
     }
 }
 

+ 401 - 36
SketchAssistant/SketchAssistant/Form1.cs

@@ -7,6 +7,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Forms;
+using System.Text.RegularExpressions;
 
 
 // This is the code for your desktop app.
@@ -19,36 +20,89 @@ namespace SketchAssistant
         public Form1()
         {
             InitializeComponent();
+            fileImporter = new FileImporter(this);
         }
 
         /**********************************/
         /*** CLASS VARIABLES START HERE ***/
         /**********************************/
 
-        //Different Program States
+        //important: add new variables only at the end of the list to keep the order of definition consistent with the order in which they are returned by GetAllVariables()
+
+        /// <summary>
+        /// Different Program States
+        /// </summary>
         public enum ProgramState
         {
             Idle,
-            Draw
+            Draw,
+            Delete
         }
-        //Current Program State
+        /// <summary>
+        /// Current Program State
+        /// </summary>
         private ProgramState currentState;
-        //Dialog to select a file.
-        OpenFileDialog openFileDialogLeft = new OpenFileDialog();
-        //Image loaded on the left
-        Image leftImage = null;
-        //Image on the right
+        /// <summary>
+        /// instance of FileImporter to handle drawing imports
+        /// </summary>
+        private FileImporter fileImporter;
+        /// <summary>
+        /// Dialog to select a file.
+        /// </summary>
+        OpenFileDialog openFileDialog = new OpenFileDialog();
+        /// <summary>
+        /// Image loaded on the left
+        /// </summary>
+        private Image leftImage = null;
+        /// <summary>
+        /// the graphic shown in the left window, represented as a list of polylines
+        /// </summary>
+        private List<Line> leftLineList;
+        /// <summary>
+        /// Image on the right
+        /// </summary>
         Image rightImage = null;
-        //Current Line being Drawn
+        /// <summary>
+        /// Current Line being Drawn
+        /// </summary>
         List<Point> currentLine;
-        //All Lines in the current session
-        List<Tuple<bool,Line>> lineList = new List<Tuple<bool, Line>>();
-        //Whether the Mouse is currently pressed in the rightPictureBox
+        /// <summary>
+        /// All Lines in the current session
+        /// </summary>
+        List<Tuple<bool,Line>> rightLineList = new List<Tuple<bool, Line>>();
+        /// <summary>
+        /// Whether the Mouse is currently pressed in the rightPictureBox
+        /// </summary>
         bool mousePressed = false;
-        //The Position of the Cursor in the right picture box
+        /// <summary>
+        /// The Position of the Cursor in the right picture box
+        /// </summary>
         Point currentCursorPosition;
-        //The graphic representation of the right image
-        Graphics graph = null;
+        /// <summary>
+        /// The Previous Cursor Position in the right picture box
+        /// </summary>
+        Point previousCursorPosition;
+        /// <summary>
+        /// Queue for the cursorPositions
+        /// </summary>
+        Queue<Point> cursorPositions = new Queue<Point>();
+        /// <summary>
+        /// The graphic representation of the right image
+        /// </summary>
+        Graphics rightGraph = null;
+        /// <summary>
+        /// Deletion Matrixes for checking postions of lines in the image
+        /// </summary>
+        bool[,] isFilledMatrix;
+        HashSet<int>[,] linesMatrix;
+        /// <summary>
+        /// Size of deletion area
+        /// </summary>
+        uint deletionSize = 2;
+        /// <summary>
+        /// History of Actions
+        /// </summary>
+        ActionHistory historyOfActions;
 
         /******************************************/
         /*** FORM SPECIFIC FUNCTIONS START HERE ***/
@@ -58,6 +112,8 @@ namespace SketchAssistant
         {
             currentState = ProgramState.Idle;
             this.DoubleBuffered = true;
+            historyOfActions = new ActionHistory(null);
+            UpdateButtonStatus();
         }
 
         //Resize Function connected to the form resize event, will refresh the form when it is resized
@@ -69,18 +125,42 @@ namespace SketchAssistant
         //Load button, will open an OpenFileDialog
         private void loadToolStripMenuItem_Click(object sender, EventArgs e)
         {
-            openFileDialogLeft.Filter = "Image|*.jpg;*.png;*.jpeg";
-            if(openFileDialogLeft.ShowDialog() == DialogResult.OK)
+            openFileDialog.Filter = "Image|*.jpg;*.png;*.jpeg";
+            if(openFileDialog.ShowDialog() == DialogResult.OK)
             {
-                toolStripLoadStatus.Text = openFileDialogLeft.SafeFileName;
-                leftImage = Image.FromFile(openFileDialogLeft.FileName);
+                toolStripLoadStatus.Text = openFileDialog.SafeFileName;
+                leftImage = Image.FromFile(openFileDialog.FileName);
                 pictureBoxLeft.Image = leftImage;
                 //Refresh the left image box when the content is changed
                 this.Refresh();
             }
+            UpdateButtonStatus();
         }
 
-        //Changes The State of the Program to drawing
+        /// <summary>
+        /// Import button, will open an OpenFileDialog
+        /// </summary>
+        private void examplePictureToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            openFileDialog.Filter = "Interactive Sketch-Assistant Drawing|*.isad";
+            if (openFileDialog.ShowDialog() == DialogResult.OK)
+            {
+                toolStripLoadStatus.Text = openFileDialog.SafeFileName;
+                try
+                {
+                    (int, int, List<Line>) values = fileImporter.ParseISADInputFile(openFileDialog.FileName);
+                    DrawEmptyCanvasLeft(values.Item1, values.Item2);
+                    BindAndDrawLeftImage(values.Item3);
+                    this.Refresh();
+                }
+                catch(FileImporterException ex)
+                {
+                    ShowInfoMessage(ex.ToString());
+                }
+            }
+        }
+
+        //Changes the state of the program to drawing
         private void drawButton_Click(object sender, EventArgs e)
         {
             if(rightImage != null)
@@ -94,6 +174,87 @@ namespace SketchAssistant
                     ChangeState(ProgramState.Draw);
                 }
             }
+            UpdateButtonStatus();
+        }
+
+        //Changes the state of the program to deletion
+        private void deleteButton_Click(object sender, EventArgs e)
+        {
+            if (rightImage != null)
+            {
+                if (currentState.Equals(ProgramState.Delete))
+                {
+                    ChangeState(ProgramState.Idle);
+                }
+                else
+                {
+                    ChangeState(ProgramState.Delete);
+                }
+            }
+            UpdateButtonStatus();
+        }
+
+        //Undo an action
+        private void undoButton_Click(object sender, EventArgs e)
+        {
+            if (historyOfActions.CanUndo())
+            {
+                HashSet<int> affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
+                SketchAction.ActionType  undoAction = historyOfActions.GetCurrentAction().GetActionType();
+                switch (undoAction)
+                {
+                    case SketchAction.ActionType.Delete:
+                        //Deleted Lines need to be shown
+                        ChangeLines(affectedLines, true);
+                        break;
+                    case SketchAction.ActionType.Draw:
+                        //Drawn lines need to be hidden
+                        ChangeLines(affectedLines, false);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            historyOfActions.MoveAction(true);
+            UpdateButtonStatus();
+        }
+
+        //Redo an action
+        private void redoButton_Click(object sender, EventArgs e)
+        {
+            if (historyOfActions.CanRedo())
+            {
+                historyOfActions.MoveAction(false);
+                HashSet<int> affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
+                SketchAction.ActionType redoAction = historyOfActions.GetCurrentAction().GetActionType();
+                switch (redoAction)
+                {
+                    case SketchAction.ActionType.Delete:
+                        //Deleted Lines need to be redeleted
+                        ChangeLines(affectedLines, false);
+                        break;
+                    case SketchAction.ActionType.Draw:
+                        //Drawn lines need to be redrawn
+                        ChangeLines(affectedLines, true);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            UpdateButtonStatus();
+        }
+
+        //Detect Keyboard Shortcuts
+        private void Form1_KeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Z)
+            {
+                undoButton_Click(sender, e);
+            }
+            if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Y)
+            {
+                redoButton_Click(sender, e);
+            }
         }
 
         //get current Mouse positon within the right picture box
@@ -112,14 +273,18 @@ namespace SketchAssistant
             }
         }
 
-        //Lift left mouse button to stop drawing.
+        //Lift left mouse button to stop drawing and add a new Line.
         private void pictureBoxRight_MouseUp(object sender, MouseEventArgs e)
         {
             mousePressed = false;
-            if (currentState.Equals(ProgramState.Draw))
+            if (currentState.Equals(ProgramState.Draw) && currentLine.Count > 0)
             {
-                lineList.Add(new Tuple<bool, Line>(true, new Line(currentLine)));
+                Line newLine = new Line(currentLine, rightLineList.Count);
+                rightLineList.Add(new Tuple<bool, Line>(true, newLine));
+                newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
+                historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID()));
             }
+            UpdateButtonStatus();
         }
 
         //Button to create a new Canvas. Will create an empty image 
@@ -127,19 +292,61 @@ namespace SketchAssistant
         //If there is no image loaded the canvas will be the size of the right picture box
         private void canvasButton_Click(object sender, EventArgs e)
         {
-            DrawEmptyCanvas();
+            if (!historyOfActions.IsEmpty())
+            {
+                if (MessageBox.Show("You have unsaved changes, creating a new canvas will discard these.", 
+                    "Attention", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK)
+                {
+                    historyOfActions = new ActionHistory(lastActionTakenLabel);
+                    DrawEmptyCanvasRight();
+                    //The following lines cannot be in DrawEmptyCanvas()
+                    isFilledMatrix = new bool[rightImage.Width, rightImage.Height];
+                    linesMatrix = new HashSet<int>[rightImage.Width, rightImage.Height];
+                    rightLineList = new List<Tuple<bool, Line>>();
+                }
+            }
+            else
+            {
+                historyOfActions = new ActionHistory(lastActionTakenLabel);
+                DrawEmptyCanvasRight();
+                //The following lines cannot be in DrawEmptyCanvas()
+                isFilledMatrix = new bool[rightImage.Width, rightImage.Height];
+                linesMatrix = new HashSet<int>[rightImage.Width, rightImage.Height];
+                rightLineList = new List<Tuple<bool, Line>>();
+            }
+            UpdateButtonStatus();
         }
 
         //add a Point on every tick to the Drawpath
-        private void drawTimer_Tick(object sender, EventArgs e)
+        private void mouseTimer_Tick(object sender, EventArgs e)
         {
+            cursorPositions.Enqueue(currentCursorPosition);
+            previousCursorPosition = cursorPositions.Dequeue();
             if (currentState.Equals(ProgramState.Draw) && mousePressed)
             {
                 currentLine.Add(currentCursorPosition);
                 Line drawline = new Line(currentLine);
-                drawline.DrawLine(graph);
+                drawline.DrawLine(rightGraph);
                 pictureBoxRight.Image = rightImage;
             }
+            if (currentState.Equals(ProgramState.Delete) && mousePressed)
+            {
+                List<Point> uncheckedPoints = Line.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
+                foreach (Point currPoint in uncheckedPoints)
+                {
+                    HashSet<int> linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionSize);
+                    if (linesToDelete.Count > 0)
+                    {
+                        historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Delete, linesToDelete));
+                        foreach (int lineID in linesToDelete)
+                        {
+                            rightLineList[lineID] = new Tuple<bool, Line>(false, rightLineList[lineID].Item2);
+                        }
+                        RepopulateDeletionMatrixes();
+                        RedrawRightImage();
+                    }
+                }
+            }
         }
 
         /***********************************/
@@ -149,39 +356,90 @@ namespace SketchAssistant
         /// <summary>
         /// Creates an empty Canvas
         /// </summary>
-        private void DrawEmptyCanvas()
+        private void DrawEmptyCanvasRight()
         {
             if (leftImage == null)
             {
                 rightImage = new Bitmap(pictureBoxRight.Width, pictureBoxRight.Height);
-                graph = Graphics.FromImage(rightImage);
-                graph.FillRectangle(Brushes.White, 0, 0, pictureBoxRight.Width + 10, pictureBoxRight.Height + 10);
+                rightGraph = Graphics.FromImage(rightImage);
+                rightGraph.FillRectangle(Brushes.White, 0, 0, pictureBoxRight.Width + 10, pictureBoxRight.Height + 10);
                 pictureBoxRight.Image = rightImage;
             }
             else
             {
                 rightImage = new Bitmap(leftImage.Width, leftImage.Height);
-                graph = Graphics.FromImage(rightImage);
-                graph.FillRectangle(Brushes.White, 0, 0, leftImage.Width + 10, leftImage.Height + 10);
+                rightGraph = Graphics.FromImage(rightImage);
+                rightGraph.FillRectangle(Brushes.White, 0, 0, leftImage.Width + 10, leftImage.Height + 10);
                 pictureBoxRight.Image = rightImage;
             }
             this.Refresh();
             pictureBoxRight.Refresh();
         }
 
+        /// <summary>
+        /// Creates an empty Canvas on the left
+        /// </summary>
+        /// <param name="width"> width of the new canvas in pixels </param>
+        /// <param name="height"> height of the new canvas in pixels </param>
+        private void DrawEmptyCanvasLeft(int width, int height)
+        {
+            if (width == 0)
+            {
+                leftImage = new Bitmap(pictureBoxLeft.Width, pictureBoxLeft.Height);
+            }
+            else
+            {
+                leftImage = new Bitmap(width, height);
+            }
+            Graphics.FromImage(leftImage).FillRectangle(Brushes.White, 0, 0, pictureBoxLeft.Width + 10, pictureBoxLeft.Height + 10);
+            pictureBoxLeft.Image = leftImage;
+            
+            this.Refresh();
+            pictureBoxLeft.Refresh();
+        }
+
         /// <summary>
         /// Redraws all lines in lineList, for which their associated boolean value equals true.
         /// </summary>
         private void RedrawRightImage()
         {
-            DrawEmptyCanvas();
-            foreach (Tuple<bool, Line> lineBoolTuple in lineList)
+            DrawEmptyCanvasRight();
+            foreach (Tuple<bool, Line> lineBoolTuple in rightLineList)
             {
                 if (lineBoolTuple.Item1)
                 {
-                    lineBoolTuple.Item2.DrawLine(graph);
+                    lineBoolTuple.Item2.DrawLine(rightGraph);
+                }
+            }
+            pictureBoxRight.Refresh();
+        }
+
+        /// <summary>
+        /// Change the status of whether or not the lines are shown.
+        /// </summary>
+        /// <param name="lines">The HashSet containing the affected Line IDs.</param>
+        /// <param name="shown">True if the lines should be shown, false if they should be hidden.</param>
+        private void ChangeLines(HashSet<int> lines, bool shown)
+        {
+            foreach (int lineId in lines)
+            {
+                if (lineId <= rightLineList.Count - 1 && lineId >= 0)
+                {
+                    rightLineList[lineId] = new Tuple<bool, Line>(shown, rightLineList[lineId].Item2);
                 }
             }
+            RedrawRightImage();
+        }
+
+        /// <summary>
+        /// Updates the active status of buttons. Currently draw, delete, undo and redo button.
+        /// </summary>
+        private void UpdateButtonStatus()
+        {
+            undoButton.Enabled = historyOfActions.CanUndo();
+            redoButton.Enabled = historyOfActions.CanRedo();
+            drawButton.Enabled = (rightImage != null);
+            deleteButton.Enabled = (rightImage != null);
         }
 
         /// <summary>
@@ -195,7 +453,11 @@ namespace SketchAssistant
             {
                 case ProgramState.Draw:
                     drawButton.CheckState = CheckState.Unchecked;
-                    drawTimer.Enabled = false;
+                    mouseTimer.Enabled = false;
+                    break;
+                case ProgramState.Delete:
+                    deleteButton.CheckState = CheckState.Unchecked;
+                    mouseTimer.Enabled = false;
                     break;
                 default:
                     break;
@@ -204,7 +466,11 @@ namespace SketchAssistant
             {
                 case ProgramState.Draw:
                     drawButton.CheckState = CheckState.Checked;
-                    drawTimer.Enabled = true;
+                    mouseTimer.Enabled = true;
+                    break;
+                case ProgramState.Delete:
+                    deleteButton.CheckState = CheckState.Checked;
+                    mouseTimer.Enabled = true;
                     break;
                 default:
                     break;
@@ -254,5 +520,104 @@ namespace SketchAssistant
             }
             return realCoordinates;
         }
+
+        /// <summary>
+        /// A function that populates the matrixes needed for deletion detection with line data.
+        /// </summary>
+        private void RepopulateDeletionMatrixes()
+        {
+            if(rightImage != null)
+            {
+                isFilledMatrix = new bool[rightImage.Width,rightImage.Height];
+                linesMatrix = new HashSet<int>[rightImage.Width, rightImage.Height];
+                foreach(Tuple<bool,Line> lineTuple in rightLineList)
+                {
+                    if (lineTuple.Item1)
+                    {
+                        lineTuple.Item2.PopulateMatrixes(isFilledMatrix, linesMatrix);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// A function that checks the deletion matrixes at a certain point 
+        /// and returns all Line ids at that point and in a square around it in a certain range.
+        /// </summary>
+        /// <param name="p">The point around which to check.</param>
+        /// <param name="range">The range around the point. If range is 0, only the point is checked.</param>
+        /// <returns>A List of all lines.</returns>
+        private HashSet<int> CheckDeletionMatrixesAroundPoint(Point p, uint range)
+        {
+            HashSet<int> returnSet = new HashSet<int>();
+
+            if (p.X >= 0 && p.Y >= 0 && p.X < rightImage.Width && p.Y < rightImage.Height)
+            {
+                if (isFilledMatrix[p.X, p.Y])
+                {
+                    returnSet.UnionWith(linesMatrix[p.X, p.Y]);
+                }
+            }
+            for (int x_mod = (int)range*(-1); x_mod < range; x_mod++)
+            {
+                for (int y_mod = (int)range * (-1); y_mod < range; y_mod++)
+                {
+                    if (p.X + x_mod >= 0 && p.Y + y_mod >= 0 && p.X + x_mod < rightImage.Width && p.Y + y_mod < rightImage.Height)
+                    {
+                        if (isFilledMatrix[p.X + x_mod, p.Y + y_mod])
+                        {
+                            returnSet.UnionWith(linesMatrix[p.X + x_mod, p.Y + y_mod]);
+                        }
+                    }
+                }
+            }
+            return returnSet;
+        }
+
+        /// <summary>
+        /// binds the given picture to templatePicture and draws it
+        /// </summary>
+        /// <param name="newTemplatePicture"> the new template picture, represented as a list of polylines </param>
+        /// <returns></returns>
+        private void BindAndDrawLeftImage(List<Line> newTemplatePicture)
+        {
+            leftLineList = newTemplatePicture;
+            foreach(Line l in leftLineList)
+            {
+                l.DrawLine(Graphics.FromImage(leftImage));
+            }
+        }
+
+        /// <summary>
+        /// shows the given info message in a popup and asks the user to aknowledge it
+        /// </summary>
+        /// <param name="message">the message to show</param>
+        private void ShowInfoMessage(String message)
+        {
+            MessageBox.Show(message);
+        }
+
+        /// <summary>
+        /// returns all instance variables in the order of their definition for testing
+        /// </summary>
+        /// <returns>all instance variables in the order of their definition</returns>
+        public Object[]/*(ProgramState, FileImporter, OpenFileDialog, Image, List<Line>, Image, List<Point>, List<Tuple<bool, Line>>, bool, Point, Point, Queue<Point>, Graphics, bool[,], HashSet<int>[,], uint, ActionHistory)*/ GetAllVariables()
+        {
+            return new Object[] { currentState, fileImporter, openFileDialog, leftImage, leftLineList, rightImage, currentLine, rightLineList, mousePressed, currentCursorPosition, previousCursorPosition, cursorPositions, rightGraph, isFilledMatrix, linesMatrix, deletionSize, historyOfActions };
+        }
+
+        /// <summary>
+        /// public method wrapper for testing purposes, invoking DrawEmptyCanvas(...) and BindAndDrawLeftImage(...)
+        /// </summary>
+        /// <param name="width">width of the parsed image</param>
+        /// <param name="height">height of the parsed image</param>
+        /// <param name="newImage">the parsed image</param>
+        public void CreateCanvasAndSetPictureForTesting(int width, int height, List<Line> newImage)
+        {
+            DrawEmptyCanvasLeft(width, height);
+            BindAndDrawLeftImage(newImage);
+        }
+
+
     }
 }

+ 46 - 1
SketchAssistant/SketchAssistant/Form1.resx

@@ -155,6 +155,21 @@
         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
         TgDQASA1MVpwzwAAAABJRU5ErkJggg==
+</value>
+  </data>
+  <data name="deleteButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
+        YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
+        0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
+        bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
+        VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
+        c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
+        Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
+        mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
+        kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
+        TgDQASA1MVpwzwAAAABJRU5ErkJggg==
 </value>
   </data>
   <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
@@ -163,9 +178,39 @@
   <metadata name="backgroundWorker2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>622, 17</value>
   </metadata>
-  <metadata name="drawTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="mouseTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>786, 17</value>
   </metadata>
+  <data name="undoButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
+        YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
+        0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
+        bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
+        VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
+        c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
+        Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
+        mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
+        kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
+        TgDQASA1MVpwzwAAAABJRU5ErkJggg==
+</value>
+  </data>
+  <data name="redoButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
+        YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
+        0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
+        bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
+        VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
+        c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
+        Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
+        mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
+        kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
+        TgDQASA1MVpwzwAAAABJRU5ErkJggg==
+</value>
+  </data>
   <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>47</value>
   </metadata>

+ 222 - 2
SketchAssistant/SketchAssistant/Line.cs

@@ -7,13 +7,44 @@ using System.Drawing;
 
 namespace SketchAssistant
 {
-    class Line
+    public class Line
     {
+        /// <summary>
+        /// list saving all the points of the line in the order of the path from start to end point
+        /// </summary>
         private List<Point> linePoints;
+        /// <summary>
+        /// unique identifier of this Line object
+        /// </summary>
+        private int identifier;
+        /// <summary>
+        /// flag showing if this is only a temporary line
+        /// </summary>
+        private bool isTemporary;
 
+        /// <summary>
+        /// The constructor for lines which are only temporary.
+        /// If you want nice lines use the other constructor.
+        /// </summary>
+        /// <param name="points">The points of the line</param>
         public Line(List<Point> points)
         {
             linePoints = new List<Point>(points);
+            isTemporary = true;
+        }
+
+        /// <summary>
+        /// The constructor for lines, which will be more resource efficient 
+        /// and have the ability to populate deletion matrixes.
+        /// </summary>
+        /// <param name="points">The points of the line</param>
+        /// <param name="id">The identifier of the line</param>
+        public Line(List<Point> points, int id)
+        {
+            linePoints = new List<Point>(points);
+            identifier = id;
+            CleanPoints();
+            isTemporary = false;
         }
 
         public Point GetStartPoint()
@@ -26,6 +57,16 @@ namespace SketchAssistant
             return linePoints.Last();
         }
 
+        public List<Point> GetPoints()
+        {
+            return linePoints;
+        }
+
+        public int GetID()
+        {
+            return identifier;
+        }
+
         /// <summary>
         /// A function that takes a Graphics element and returns it with
         /// the line drawn on it.
@@ -39,8 +80,187 @@ namespace SketchAssistant
             {
                 canvas.DrawLine(thePen, linePoints[i], linePoints[i + 1]);
             }
-            //canvas.DrawLine(thePen, linePoints[linePoints.Count-1], linePoints[linePoints.Count]);
+            //If there is only one point
+            if(linePoints.Count == 1){ canvas.FillRectangle(Brushes.Black, linePoints[0].X, linePoints[0].Y, 1, 1); }
             return canvas;
         }
+
+        /// <summary>
+        /// A function that will take to matrixes and populate the with the line data of this line object
+        /// </summary>
+        /// <param name="boolMatrix">The Matrix of booleans, in which is saved wether there is a line at this position.</param>
+        /// <param name="listMatrix">The Matrix of Lists of integers, in which is saved which lines are at this position</param>
+        public void PopulateMatrixes(bool[,] boolMatrix, HashSet<int>[,] listMatrix)
+        {
+            if(!isTemporary)
+            {
+                foreach (Point currPoint in linePoints)
+                {
+                    if (currPoint.X >= 0 && currPoint.Y >= 0 && 
+                        currPoint.X < boolMatrix.GetLength(0) && currPoint.Y < boolMatrix.GetLength(1))
+                    {
+                        boolMatrix[currPoint.X, currPoint.Y] = true;
+                        if (listMatrix[currPoint.X, currPoint.Y] == null)
+                        {
+                            listMatrix[currPoint.X, currPoint.Y] = new HashSet<int>();
+                        }
+                        listMatrix[currPoint.X, currPoint.Y].Add(identifier);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Removes duplicate points from the line object
+        /// </summary>
+        private void CleanPoints()
+        {
+            if (linePoints.Count > 1)
+            {
+                List<Point> newList = new List<Point>();
+                List<Point> tempList = new List<Point>();
+                //Since Point is non-nullable, we must ensure the nullPoints, 
+                //which we remove can not possibly be points of the original given line.
+                int nullValue = linePoints[0].X + 1;
+                //Fill the gaps between points
+                for (int i = 0; i < linePoints.Count - 1; i++)
+                {
+                    nullValue += linePoints[i + 1].X;
+                    List<Point> partialList = BresenhamLineAlgorithm(linePoints[i], linePoints[i + 1]);
+                    tempList.AddRange(partialList);
+                }
+                Point nullPoint = new Point(nullValue, 0);
+                //Set duplicate points to the null point
+                for (int i = 1; i < tempList.Count; i++)
+                {
+                    if ((tempList[i].X == tempList[i - 1].X) && (tempList[i].Y == tempList[i - 1].Y))
+                    {
+                        tempList[i - 1] = nullPoint;
+                    }
+                }
+                //remove the null points
+                foreach (Point tempPoint in tempList)
+                {
+                    if (tempPoint.X != nullValue)
+                    {
+                        newList.Add(tempPoint);
+                    }
+                }
+                linePoints = new List<Point>(newList);
+            }
+        }
+
+        /// <summary>
+        /// An implementation of the Bresenham Line Algorithm, 
+        /// which calculates all points between two points in a straight line.
+        /// Implemented using the pseudocode on Wikipedia.
+        /// </summary>
+        /// <param name="p0">The start point</param>
+        /// <param name="p1">The end point</param>
+        /// <returns>All points between p0 and p1 (including p0 and p1)</returns>
+        public static List<Point> BresenhamLineAlgorithm(Point p0, Point p1)
+        {
+            int deltaX = p1.X - p0.X;
+            int deltaY = p1.Y - p0.Y;
+            List<Point> returnList;
+
+            if (Math.Abs(deltaY) < Math.Abs(deltaX))
+            {
+                if(p0.X > p1.X)
+                {
+                    returnList = GetLineLow(p1.X, p1.Y, p0.X, p0.Y);
+                    returnList.Reverse();
+                }
+                else
+                {
+                    returnList = GetLineLow(p0.X, p0.Y, p1.X, p1.Y);
+                }
+            }
+            else
+            {
+                if (p0.Y > p1.Y)
+                {
+                    returnList = GetLineHigh(p1.X, p1.Y, p0.X, p0.Y);
+                    returnList.Reverse();
+                }
+                else
+                {
+                    returnList = GetLineHigh(p0.X, p0.Y, p1.X, p1.Y);
+                }
+            }
+            return returnList;
+        }
+
+        /// <summary>
+        /// Helping function of the Bresenham Line algorithm,
+        /// under the assumption that abs(deltaY) is smaller than abs(deltX)
+        /// and x0 is smaller than x1
+        /// </summary>
+        /// <param name="x0">x value of point 0</param>
+        /// <param name="y0">y value of point 0</param>
+        /// <param name="x1">x value of point 1</param>
+        /// <param name="y1">y value of point 1</param>
+        /// <returns>All points on the line between the two points</returns>
+        private static List<Point> GetLineLow(int x0, int y0, int x1, int y1)
+        {
+            List<Point> returnList = new List<Point>();
+            int dx = x1 - x0;
+            int dy = y1 - y0;
+            int yi = 1;
+            if(dy < 0)
+            {
+                yi = -1;
+                dy = -dy;
+            }
+            int D = 2 * dy - dx;
+            int y = y0;
+            for (int x = x0; x <= x1; x++)
+            {
+                returnList.Add(new Point(x, y));
+                if (D > 0)
+                {
+                    y = y + yi;
+                    D = D - 2 * dx;
+                }
+                D = D + 2 * dy;
+            }
+            return returnList;
+        }
+
+        /// <summary>
+        /// Helping function of the Bresenham Line algorithm,
+        /// under the assumption that abs(deltaY) is larger or equal than abs(deltX)
+        /// and y0 is smaller than y1
+        /// </summary>
+        /// <param name="x0">x value of point 0</param>
+        /// <param name="y0">y value of point 0</param>
+        /// <param name="x1">x value of point 1</param>
+        /// <param name="y1">y value of point 1</param>
+        /// <returns>All points on the line between the two points</returns>
+        private static List<Point> GetLineHigh(int x0, int y0, int x1, int y1)
+        {
+            List<Point> returnList = new List<Point>();
+            int dx = x1 - x0;
+            int dy = y1 - y0;
+            int xi = 1;
+            if (dx < 0)
+            {
+                xi = -1;
+                dx = -dx;
+            }
+            int D = 2 * dx - dy;
+            int x = x0;
+            for (int y = y0; y <= y1; y++)
+            {
+                returnList.Add(new Point(x, y));
+                if (D > 0)
+                {
+                    x = x + xi;
+                    D = D - 2 * dy;
+                }
+                D = D + 2 * dx;
+            }
+            return returnList;
+        }
     }
 }

+ 98 - 0
SketchAssistant/SketchAssistant/SketchAction.cs

@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SketchAssistant
+{
+    public class SketchAction
+    {
+        //Types of possible actions
+        public enum ActionType
+        {
+            Draw,
+            Delete,
+            Start
+        }
+        //Type of this action
+        private ActionType thisAction;
+        //ID of the Line affected
+        private HashSet<int> lineIDs;
+
+        /// <summary>
+        /// Constructor for a new action with multiple lines affected.
+        /// </summary>
+        /// <param name="theAction">The type of action, if it is ActionType.Start the affectedIDs will be ignored.</param>
+        /// <param name="affectedID">The IDs of the lines affected.</param>
+        public SketchAction(ActionType theAction, HashSet<int> affectedIDs)
+        {
+            thisAction = theAction;
+            if (theAction.Equals(ActionType.Start)) { lineIDs = new HashSet<int>(); }
+            else { lineIDs = new HashSet<int>(affectedIDs); }
+        }
+
+        /// <summary>
+        /// Constructor for a new action with one line affected.
+        /// </summary>
+        /// <param name="theAction">The type of action, if it is ActionType.Start the affectedID will be ignored.</param>
+        /// <param name="affectedID">The ID of the affected line.</param>
+        public SketchAction(ActionType theAction, int affectedID)
+        {
+            thisAction = theAction;
+            if (theAction.Equals(ActionType.Start)) { lineIDs = new HashSet<int>(); }
+            else
+            {
+                lineIDs = new HashSet<int>();
+                lineIDs.Add(affectedID);
+            }
+        }
+
+        /// <summary>
+        /// Fetches the type of this action.
+        /// </summary>
+        /// <returns>The type of this action.</returns>
+        public ActionType GetActionType()
+        {
+            return thisAction;
+        }
+
+        /// <summary>
+        /// Fetches the IDs of the lines affected by this action.
+        /// </summary>
+        /// <returns>The IDs of the lines affected by this action. An empty set if there is no line affected.</returns>
+        public HashSet<int> GetLineIDs()
+        {
+            return lineIDs;
+        }
+
+        /// <summary>
+        /// Get the information about this action.
+        /// </summary>
+        /// <returns>A String describing what happend at this action.</returns>
+        public String GetActionInformation()
+        {
+            String returnString;
+            switch (thisAction)
+            {
+                case ActionType.Start:
+                    returnString = "A new canvas was created.";
+                    break;
+                case ActionType.Draw:
+                    returnString = "Line number " + lineIDs.First().ToString() + " was drawn.";
+                    break;
+                case ActionType.Delete:
+                    if (lineIDs.Count == 1) { returnString = "Line number " + lineIDs.First().ToString() + " was deleted."; }
+                    else
+                    {
+                        returnString = "Several Lines were deleted.";
+                    }
+                    break;
+                default:
+                    returnString = "There is no information available for this action.";
+                    break;
+            }
+            return returnString;
+        }
+    }
+}

+ 16 - 1
SketchAssistant/SketchAssistant/SketchAssistant.csproj

@@ -26,6 +26,8 @@
     <IsWebBootstrapper>false</IsWebBootstrapper>
     <UseApplicationTrust>false</UseApplicationTrust>
     <BootstrapperEnabled>true</BootstrapperEnabled>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
@@ -48,7 +50,14 @@
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
+    <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="Microsoft.CSharp" />
@@ -60,6 +69,10 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="FileImporter.cs" />
+    <Compile Include="FileImporterException.cs" />
+    <Compile Include="SketchAction.cs" />
+    <Compile Include="ActionHistory.cs" />
     <Compile Include="Line.cs" />
     <Compile Include="Form1.cs">
       <SubType>Form</SubType>
@@ -93,7 +106,9 @@
     </Compile>
   </ItemGroup>
   <ItemGroup>
-    <None Include="App.config" />
+    <None Include="App.config">
+      <SubType>Designer</SubType>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <BootstrapperPackage Include=".NETFramework,Version=v4.6.1">

+ 3 - 2
SketchAssistant/SketchAssistant/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Svg" version="2.3.0" targetFramework="net461" />
-  <package id="SvgNet" version="1.0.8" targetFramework="net461" />
+  <package id="OpenCover" version="4.6.519" targetFramework="net461" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net461" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net461" />
 </packages>