21 Commits 2845142baf ... 5314a6da53

Author SHA1 Message Date
  videowall-pc-user 5314a6da53 Display first point of every line 5 years ago
  Martin Edlund 409577c008 commented out assert 5 years ago
  Martin Edlund 2b127c2b2c Fixed appveyor yml 5 years ago
  Vincenz Mechler 0098967e2f fixed minor bugs in FileImporter 5 years ago
  Martin Edlund d5158d679d Added check for com5 5 years ago
  Vincenz Mechler 4856b1bdaa added try catch to cpp project to hopefully handle not connected motors when trying to initialize 5 years ago
  videowall-pc-user 59ed1ad6c7 minor changes 5 years ago
  Vincenz Mechler 7cd0ca0a0c made armband vibrate 5 years ago
  Martin Edlund (Laptop) 731bc1311e Merge branch 'develop' into userstory22 5 years ago
  m-edlund 4178ea6066 Deleted CustomCanvas 5 years ago
  m-edlund dc01cc6b94 Userstory20 (#17) 5 years ago
  Vincenz Mechler 6e2b672b2c added basic vibrotactile feedback for surface simulation 5 years ago
  Vincenz Mechler 02bb8cab48 completed StaticLibMotors dll (now representing a full interface to BodyActuator.dll), added documentation to staticLibMotors and added documentation to LocalArmbandInterface 5 years ago
  videowall-pc-user 7905509a6e removed parameters from actuate functions 5 years ago
  videowall-pc-user c70efe74f7 actuate works with 3 intensities: 100%, 66%, 33% 5 years ago
  Dennymany b1f1faf827 added function to delete existing armband 5 years ago
  Dennymany 9d3ad3a705 added actuate function 5 years ago
  Dennymany 02eaac3898 added actuate funtction to dll 5 years ago
  m-edlund f4674b5efd Userstory13 (#16) 5 years ago
  m-edlund aee756b8f8 Hotfix (#15) 5 years ago
  m-edlund e93039223e Userstory8 (#14) 5 years ago
42 changed files with 2477 additions and 1069 deletions
  1. 15 16
      .appveyor.yml
  2. 8 8
      Finished Userstories/userstory13.md
  3. 13 0
      Finished Userstories/userstory8.md
  4. 0 0
      Neues Textdokument.txt
  5. 3 0
      SketchAssistant/.gitignore
  6. 15 3
      SketchAssistant/GenerateCoverageReport.bat
  7. BIN
      SketchAssistant/NPTrackingTools.dll
  8. 0 450
      SketchAssistant/SketchAssistant.Tests/UnitTest1.cs
  9. 0 30
      SketchAssistant/SketchAssistant/test_input_files/all.svg
  10. 2 1
      SketchAssistant/SketchAssistantWPF/App.xaml
  11. 0 18
      SketchAssistant/SketchAssistantWPF/CustomCanvas.cs
  12. 4 0
      SketchAssistant/SketchAssistantWPF/DebugData.cs
  13. 38 36
      SketchAssistant/SketchAssistantWPF/FileImporter.cs
  14. 39 0
      SketchAssistant/SketchAssistantWPF/Frame.cs
  15. 149 0
      SketchAssistant/SketchAssistantWPF/GeometryCalculator.cs
  16. 39 0
      SketchAssistant/SketchAssistantWPF/HighPerformanceTimer.cs
  17. 43 14
      SketchAssistant/SketchAssistantWPF/InternalLine.cs
  18. 97 6
      SketchAssistant/SketchAssistantWPF/LocalArmbandInterface.cs
  19. 404 103
      SketchAssistant/SketchAssistantWPF/MVP_Model.cs
  20. 290 94
      SketchAssistant/SketchAssistantWPF/MVP_Presenter.cs
  21. 15 0
      SketchAssistant/SketchAssistantWPF/MVP_View.cs
  22. 76 37
      SketchAssistant/SketchAssistantWPF/MainWindow.xaml
  23. 165 48
      SketchAssistant/SketchAssistantWPF/MainWindow.xaml.cs
  24. BIN
      SketchAssistant/SketchAssistantWPF/NPTrackingTools.dll
  25. 195 0
      SketchAssistant/SketchAssistantWPF/OptiTrackConnector.cs
  26. 1 4
      SketchAssistant/SketchAssistantWPF/SketchAction.cs
  27. 47 4
      SketchAssistant/SketchAssistantWPF/SketchAssistantWPF.csproj
  28. BIN
      SketchAssistant/SketchAssistantWPF/StaticLibMotors.dll
  29. 24 0
      SketchAssistant/SketchAssistantWPF/Wristband.cs
  30. BIN
      SketchAssistant/SketchAssistantWPF/nicubunu-Quill.ico
  31. BIN
      SketchAssistant/SketchAssistantWPF/optitrack_setup.ttp
  32. 3 2
      SketchAssistant/SketchAssistantWPF/packages.config
  33. 187 37
      SketchAssistant/StaticLib1/ArmbandInterface.cpp
  34. 62 13
      SketchAssistant/StaticLib1/ArmbandInterface.h
  35. 494 140
      SketchAssistant/WhiteTests/UITest.cs
  36. 4 2
      SketchAssistant/WhiteTests/WhiteTests.csproj
  37. 0 3
      SketchAssistant/WhiteTests/packages.config
  38. 3 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/H_V_C_S.svg
  39. 8 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/circle_h_v_c_s.svg
  40. 21 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/path2.svg
  41. BIN
      SketchAssistant/optitrack_setup.ttp
  42. 13 0
      userstory20.md

+ 15 - 16
.appveyor.yml

@@ -1,27 +1,26 @@
 image:
- - Visual Studio 2017
- 
+    - Visual Studio 2017
+
 install:
- - "nuget install TestStack.White"
- - "nuget install MSTest.TestAdapter"
- - "nuget install MSTest.TestFramework"
- - "nuget restore SketchAssistant/SketchAssistant.sln"
- 
+    - "nuget install TestStack.White"
+    - "nuget install MSTest.TestAdapter"
+    - "nuget install MSTest.TestFramework"
+    - "nuget restore SketchAssistant/SketchAssistant.sln"
+
 before_build:
- - ps: .\screenres.ps1
- 
+    - ps: .\screenres.ps1
+
 artifacts:
- - path: \SketchAssistant\GeneratedReports\
+    - path: \SketchAssistant\GeneratedReports\
 #  - path: SketchAssistant/WhiteTests/test_input_files/whitelisted/*.svg
 #    name: whitelisted svg files for testing
 #  - path: SketchAssistant/WhiteTests/test_input_files/blacklisted/*.svg
 #    name: blacklisted svg files for testing
 
 before_test:
- - ps: "SketchAssistant/GenerateCoverageReport.bat"
-
+    - ps: "SketchAssistant/GenerateCoverageReport.bat"
+#don't run tests depending on [DeploymentItem] and filesystem access
 test:
-    #don't run tests depending on [DeploymentItem] and filesystem access
-    categories:
-        except:
-            - FileIO
+ categories:
+  except:
+   - Local

+ 8 - 8
Finished Userstories/userstory13.md

@@ -1,13 +1,13 @@
-# Userstory 13 
+# Userstory 13 
  
 |**ID**|13|  
 |-|-|
-|**Name**|Start und Endpunkt anzeigen|
-|**Beschreibung**|Beim Nachzeichnen kann der Start- & Endpunkt einer nachzuzeichnenden Linie angzeigt werden.|
-|**Akzeptanzkriterium**|Sofern auf der linken Seite eine nachzeichenbare Grafik dargestellt wird, kann der Start- und Endpunkt der ersten Linie der linken Grafik an der richtigen Stelle auf der rechten Seite angezeigt werden.|
+|**Name**|Overlay Unterstützung|
+|**Beschreibung**|Als Nutzer möchte ich, dass beim Nachzeichnen, der Start- & Endpunkt einer nachzuzeichnenden Linie angzeigt werden kann, sowie bei Verwendung des Optitrack Systems ein Cursor angezeigt werden kann.|
+|**Akzeptanzkriterium**|Es ist möglich Overlay Elemente anzuzeigen und frei zu positionieren.|
 |Geschätzter Aufwand (Story Points)|2|
-|Entwickler|Martin|
-|Umgesetzt in Iteration|4|
-|Tatsächlicher Aufwand (Std.)|1|
-|Velocity (Std./Story Point)|0.5|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|15|
+|Tatsächlicher Aufwand (Std.)|8|
+|Velocity (Std./Story Point)|4|
 |Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory8.md

@@ -0,0 +1,13 @@
+# Userstory 8
+
+|**ID**|8|
+|-|-|
+|**Name**|Vergleich vom gezeichneten Bild mit der Ursprungsgrafik|
+|**Beschreibung**|Ein Feature, das die vom Nutzer gezeichnete Grafik, mit der geladenen Grafik vergleicht.|
+|**Akzeptanzkriterium**|Eine Funktion bei der Ähnlichkeit der vom Nutzer gezeichneten Zeichnung und der geladenen Grafik in der UI angezeigt wird.|
+|Geschätzter Aufwand (Story Points)|10|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|14|
+|Tatsächlicher Aufwand (Std.)|12.5|
+|Velocity (Std./Story Point)|1.25|
+|Bemerkungen|Keine|

+ 0 - 0
Neues Textdokument.txt


+ 3 - 0
SketchAssistant/.gitignore

@@ -0,0 +1,3 @@
+/GeneratedReports/
+!*.ico
+!*.svg

+ 15 - 3
SketchAssistant/GenerateCoverageReport.bat

@@ -1,13 +1,25 @@
 if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"
 
-"%~dp0\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe" ^
+for /d %%a in (
+  "%~dp0\packages\OpenCover.*"
+) do set "openCoverFolder=%%~fa\"
+
+for /d %%a in (
+  "%~dp0\packages\Microsoft.TestPlatform.*"
+) do set "microPlat=%%~fa\"
+
+for /d %%a in (
+  "%~dp0\packages\ReportGenerator.*"
+) do set "repGen=%%~fa\"
+
+"%openCoverFolder%\tools\OpenCover.Console.exe" ^
 -register:user ^
--target:"%~dp0\packages\Microsoft.TestPlatform.16.0.0\tools\net451\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" ^
+-target:"%microPlat%\tools\net451\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" ^
 -targetargs:"%~dp0\WhiteTests\bin\Debug\WhiteTests.dll" ^
 -filter:"+[SketchAssistantWPF*]*" ^
 -mergebyhash ^
 -output:"%~dp0\GeneratedReports\opencovertests.xml"
 
-"%~dp0\packages\ReportGenerator.4.0.14\tools\net47\ReportGenerator.exe" ^
+"%repGen%\tools\net47\ReportGenerator.exe" ^
 -reports:"%~dp0\GeneratedReports\opencovertests.xml" ^
 -targetdir:"%~dp0\GeneratedReports\ReportGeneratorOutput"

BIN
SketchAssistant/NPTrackingTools.dll


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

@@ -1,450 +0,0 @@
-using System;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System.Drawing;
-using System.Collections.Generic;
-using SketchAssistant;
-using System.Windows.Forms;
-using System.IO;
-
-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.GeometryCalculator.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.GeometryCalculator.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.GeometryCalculator.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.GeometryCalculator.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.GeometryCalculator.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.GeometryCalculator.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.GeometryCalculator.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
-    {
-
-        private ActionHistory GetActionHistory()
-        {
-            return new ActionHistory();
-        }
-
-        [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());
-            var lastActionLabel = testHistory.MoveAction(false);
-            Assert.AreEqual(actionType, testHistory.GetCurrentAction().GetActionType());
-            Assert.AreEqual(message, lastActionLabel);
-        }
-    }
-
-    [TestClass]
-    [DeploymentItem(@"SketchAssistant.Tests\test_input_files\")]
-    public class FileImporterTests
-    { 
-
-        /// <summary>
-        /// instance of TestContext to be able to access deployed files
-        /// </summary>
-         private TestContext testContextInstance;
-        /// <summary>
-        ///Gets or sets the test context which provides
-        ///information about and functionality for the current test run.
-        ///</summary>
-        public TestContext TestContext
-        {
-            get
-            {
-                return testContextInstance;
-            }
-            set
-            {
-                testContextInstance = value;
-            }
-        }
-        /*
-        [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();
-
-            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();
-            //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];
-        }
-        */
-
-        /// <summary>
-        /// parses all whitelisted files and ensures no exceptions are thrown (parsing abortion, e.g. due to corrupted input files, are realized by throwing a FileImporterException)
-        /// </summary>
-        [TestMethod]
-        public void parseSVGInputNoErrorForWhitelistedFilesTest()
-        {
-            FileImporter uut = new FileImporter();
-
-            string[] files = Directory.GetFiles(TestContext.DeploymentDirectory + @"\test_input_files\whitelisted", "*.svg", SearchOption.AllDirectories);
-            Assert.IsTrue(files.Length > 0);
-
-            foreach (string s in files) //parse each of the whitelisted files
-            {
-                bool noExceptionThrown = true;
-                try
-                {
-                    uut.ParseSVGInputFile(s, 10000, 10000);
-                }
-                catch (Exception e)
-                {
-                    noExceptionThrown = false;
-                }
-                Assert.IsTrue(noExceptionThrown);
-            }
-        }
-
-        /// <summary>
-        /// parses all blacklisted files and ensures an instance of FileIporterException is thrown for each file, but no other exceptions occur
-        /// </summary>
-        [TestMethod]
-        public void parseSVGInputNoErrorForBlacklistedFilesTest()
-        {
-            FileImporter uut = new FileImporter();
-
-            string[] files = Directory.GetFiles(TestContext.DeploymentDirectory + @"\test_input_files\blacklisted", "*.svg", SearchOption.AllDirectories);
-            Assert.IsTrue(files.Length > 0);
-            foreach (string s in files) //parse each of the blacklisted files
-            {
-                bool correctExceptionThrown = false;
-                try
-                {
-                    uut.ParseSVGInputFile(s, 10000, 10000);
-                }
-                catch(FileImporterException e)
-                {
-                    correctExceptionThrown = true;
-                }
-                catch(Exception e)
-                {
-                }
-                Assert.IsTrue(correctExceptionThrown);
-            }
-        }
-    }
-
-}

+ 0 - 30
SketchAssistant/SketchAssistant/test_input_files/all.svg

@@ -1,30 +0,0 @@
-fsfwefafaefcSC
-F
-S
-FSFewFAHBfAg
-<DSFVSF>
-<GSG FS02MAFfm="Fmseikf" />
-source: https://www.w3schools.com/
-<svg height="500" width="500">
-  <polygon points="220,10 300,210 170,250 123,234" style="fill:lime;stroke:purple;stroke-width:1" />
-  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
-  style="fill:none;stroke:black;stroke-width:3" />
-    <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
-  <rect x="50" y="20" width="150" height="150"
-  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9" />
-  <rect x="352.324" y="311.765" fill="#5486F7" width="4" height="128"/>
-  <rect x="478.871" y="266.351" fill="#FFFFFF" width="12" height="12"/>
-  <ellipse cx="250" cy="250" rx="100" ry="50"
-  style="fill:yellow;stroke:purple;stroke-width:2" />
-  <ellipse cx="240" cy="100" rx="220" ry="30" style="fill:purple" />
-  <ellipse cx="220" cy="70" rx="190" ry="20" style="fill:lime" />
-  <ellipse cx="210" cy="45" rx="170" ry="15" style="fill:yellow" />
-  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
-  stroke-width="3" fill="none" />
-  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
-  stroke-width="3" fill="none" />
-  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
-  fill="none" />
-  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
-  stroke-width="5" fill="none" />
-</svg>

+ 2 - 1
SketchAssistant/SketchAssistantWPF/App.xaml

@@ -2,7 +2,8 @@
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:SketchAssistantWPF"
-             StartupUri="MainWindow.xaml">
+             StartupUri="MainWindow.xaml"
+             ShutdownMode="OnMainWindowClose">
     <Application.Resources>
          
     </Application.Resources>

+ 0 - 18
SketchAssistant/SketchAssistantWPF/CustomCanvas.cs

@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Automation.Peers;
-using System.Windows.Controls;
-
-namespace SketchAssistantWPF
-{
-    public class CustomCanvas : Canvas
-    {
-        protected override AutomationPeer OnCreateAutomationPeer()
-        {
-            return new FrameworkElementAutomationPeer(this);
-        }
-    }
-}

File diff suppressed because it is too large
+ 4 - 0
SketchAssistant/SketchAssistantWPF/DebugData.cs


+ 38 - 36
SketchAssistant/SketchAssistantWPF/FileImporter.cs

@@ -26,7 +26,7 @@ namespace SketchAssistantWPF
         /// <summary>
         /// array containing all characters interpreted as whitespaces which seperate words/tokens in the input file
         /// </summary>
-        readonly char[] whitespaces = new char[] { ' ' , ',' };
+        readonly char[] whitespaces = new char[] { ' ', ',' };
 
         /// <summary>
         /// number of points to create along the outline of an ellipse, divisible by 4
@@ -43,7 +43,7 @@ namespace SketchAssistantWPF
         /// </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 Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
+        public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
         {
             return ParseISADInput(System.IO.File.ReadAllLines(fileName));
         }
@@ -69,7 +69,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             }
             Tuple<int, int> dimensions = ParseISADHeader(allLines);
             List<InternalLine> picture = ParseISADBody(allLines, dimensions.Item1, dimensions.Item2);
-            
+
             return new Tuple<int, int, List<InternalLine>>(dimensions.Item1, dimensions.Item2, picture);
         }
 
@@ -197,13 +197,13 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             i++;
             int width; //width of the resulting picture in pixels
             int height; //height of the resulting picture in pixels
-            if(windowWidth != 0 && windowHeight != 0)
+            if (windowWidth != 0 && windowHeight != 0)
             {
                 if (windowWidth / windowHeight > sizedef.Item1 / sizedef.Item2) //height dominant, width has to be smaller than drawing window to preserve xy-scale
                 {
                     scale = windowHeight / sizedef.Item2;
                     height = (int)Math.Round(windowHeight);
-                    width = (int) Math.Round(scale * sizedef.Item1);
+                    width = (int)Math.Round(scale * sizedef.Item1);
                 }
                 else //width dominant, height has to be smaller than drawing window to preserve xy-scale
                 {
@@ -218,7 +218,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 width = sizedef.Item1;
                 height = sizedef.Item2;
             }
-            for (int j=0; j < allLines.Length; j++)
+            for (int j = 0; j < allLines.Length; j++)
             {
                 allLines[j] = allLines[j].Trim(whitespaces);
             }
@@ -238,9 +238,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 i++;
             }
             String[] currentLine = allLines[i].Split(' ');
-            int width= -1;
-            int height= -1;
-            for(int j= 0; j < currentLine.Length; j++)
+            int width = -1;
+            int height = -1;
+            for (int j = 0; j < currentLine.Length; j++)
             {
                 if (currentLine[j].StartsWith("width"))
                 {
@@ -251,9 +251,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                     height = Convert.ToInt32(ParseSingleSVGAttribute(currentLine[j]));
                 }
             }
-            if(width == -1)
+            if (width == -1)
             {
-                throw new FileImporterException("missing width definition in SVG header", "the header should contain the \"width=...\" attribute", i+1);
+                throw new FileImporterException("missing width definition in SVG header", "the header should contain the \"width=...\" attribute", i + 1);
             }
             if (height == -1)
             {
@@ -301,7 +301,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
         /// <returns>the parsed element as a Line object, or null if the element is not supported</returns>
         private List<InternalLine> ParseSingleLineSVGElement(string[] currentElement)
         {
-            List<Point> points= null;
+            List<Point> points = null;
             List<InternalLine> element = null;
             switch (currentElement[0])
             {
@@ -350,7 +350,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             double h = 0;
             double rx = 0;
             double ry = 0;
-            for(int j= 0; j < currentElement.Length; j++)
+            for (int j = 0; j < currentElement.Length; j++)
             {
                 if (currentElement[j].StartsWith("x="))
                 {
@@ -540,9 +540,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 }
             }
             List<Point> polygon = new List<Point>();
-            for (int k = 0; k < points.Length - 1; k+=2)
+            for (int k = 0; k < points.Length - 1; k += 2)
             {
-                polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k+1], CultureInfo.InvariantCulture)));
+                polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k + 1], CultureInfo.InvariantCulture)));
             }
             polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[0], CultureInfo.InvariantCulture), Convert.ToDouble(points[1], CultureInfo.InvariantCulture))); //close polygon
             return polygon;
@@ -573,12 +573,12 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             NormalizePathDeclaration(pathElements); //expand path data to always explicitly have the command descriptor in front of the appropriate number of arguments and to seperate command descriptors, coordinates and other tokens always into seperate list elements (equivalent to seperation with spaces in the input file, but svg allows also for comma as a seperator, and for omitting seperators where possible without losing information (refer to svg grammer) to reduce file size
             List<InternalLine> element = new List<InternalLine>();
             List<Point> currentLine = new List<Point>();
-            double lastBezierControlPointX= 0;
-            double lastBezierControlPointY= 0;
+            double lastBezierControlPointX = 0;
+            double lastBezierControlPointY = 0;
             double lastPositionX;
             double lastPositionY;
-            double initialPositionX= -1;
-            double initialPositionY= -1;
+            double initialPositionX = -1;
+            double initialPositionY = -1;
             bool newSubpath = true;
             Tuple<List<Point>, double, double> valuesArc; //list of points, new values for: lastPositionX, lastPositionY
             Tuple<List<Point>, double, double, double, double> valuesBezierCurve; //list of points, new values for: lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY
@@ -588,7 +588,8 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             lastPositionX = valuesSinglePoint.Item2;
             lastPositionY = valuesSinglePoint.Item3;
             String currentToken;
-            while (!(pathElements.Count == 0)){
+            while (!(pathElements.Count == 0))
+            {
                 if (newSubpath)
                 {
                     initialPositionX = lastPositionX; //update buffers for coordinates of first point of active subpath
@@ -678,7 +679,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 }
                 else if (currentToken.Equals("c"))
                 {
-                    valuesBezierCurve = Parse_C(pathElements, lastPositionX, lastPositionY);
+                    valuesBezierCurve = Parse_c(pathElements, lastPositionX, lastPositionY);
                     currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
                     lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
                     lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
@@ -773,7 +774,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
         {
             Char lastCommand = 'M';
             int argumentCounter = 0;
-            for( int j= 0; j < pathElements.Count; j++)
+            for (int j = 0; j < pathElements.Count; j++)
             {
                 String currentElement = pathElements.ElementAt(j);
                 if (currentElement.Length != 1)
@@ -788,8 +789,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                     }
                     else if ((currentElement.First() >= '0' && currentElement.First() <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e') //seperate a single coordinate / number
                     {
-                        bool repeatCommandDescriptor = false; 
-                        switch (lastCommand){ //check for reaching of next command with omitted command descriptor
+                        bool repeatCommandDescriptor = false;
+                        switch (lastCommand)
+                        { //check for reaching of next command with omitted command descriptor
                             case 'M':
                                 if (argumentCounter >= 2) repeatCommandDescriptor = true;
                                 break;
@@ -849,7 +851,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                         {
                             pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
                             j++; //skip command descriptor (was put into active position in the list
-                            argumentCounter = 0; //reset argument counter
+                            argumentCounter = -1; //reset argument counter
                         }
                         bool decimalPointEncountered = false;
                         for (int k = 1; k < currentElement.Length; k++)
@@ -858,15 +860,15 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                             {
                                 decimalPointEncountered = true;
                             }
-                            else if (!((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e'))
+                            else if (!((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9') || currentElement.ElementAt(k) == 'e'))
                             {
                                 pathElements.RemoveAt(j);
-                                pathElements.Insert(j, currentElement.Substring(0, k - 1)); //insert number as seperate element
+                                pathElements.Insert(j, currentElement.Substring(0, k)); //insert number as seperate element
                                 pathElements.Insert(j + 1, currentElement.Substring(k)); //insert rest of String at next position so it will be processed again
                                 break;
                             }
                         }
-                        argumentCounter++; 
+                        argumentCounter++;
                     }
                     else //parse non-space seperators and skip other unsupported characters (the only other valid ones per svg standard would be weird tokens looking like format descriptors (e.g. '#xC'), these are unsopported and will likely cause an error or other inconsitencies during parsing)
                     {
@@ -888,7 +890,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                         lastCommand = currentElement.First();
                         argumentCounter = 0;
                     }
-                    else if(!(currentElement.First() >= '0' && currentElement.First() <= '9')) //not a number
+                    else if (!(currentElement.First() >= '0' && currentElement.First() <= '9')) //not a number
                     {
                         pathElements.RemoveAt(j); //remove element
                         j--; //decrement index pointer so next element will not be skipped (indices of all folowing elements just decreased by 1)
@@ -957,7 +959,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                         {
                             pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
                             j++; //skip command descriptor (was put into active position in the list
-                            argumentCounter = 0; //reset argument counter
+                            argumentCounter = -1; //reset argument counter
                         }
                         argumentCounter++;
                     }
@@ -1415,14 +1417,14 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             {
                 xPlusFlag = true; //left point lies right of line
             }
-            if(sweepFlag != largeArcFlag) //need "right" center point, not "left" one (refer to svg specification, sweepFlag means going around the circle in "clockwise" direction, largeArcFlag means tracing the larger of the two possible arcs in the selected direction) 
+            if (sweepFlag != largeArcFlag) //need "right" center point, not "left" one (refer to svg specification, sweepFlag means going around the circle in "clockwise" direction, largeArcFlag means tracing the larger of the two possible arcs in the selected direction) 
             {
                 xPlusFlag = !xPlusFlag;
                 yPlusFlag = !yPlusFlag;
             }
             double xC; // coordinates of center point of circle
             double yC;
-            if(xPlusFlag) xC = x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q); //x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((y1 - y2) / q);
+            if (xPlusFlag) xC = x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q); //x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((y1 - y2) / q);
             else xC = x3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q);
             if (yPlusFlag) yC = y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q); //y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((x2-x1) / q);
             else yC = y3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q);
@@ -1430,7 +1432,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             for (int j = 0; j < values.Item1.Length; j++)
             {
                 values.Item1[j] = values.Item1[j] + xC; //correct center point coordinate bias
-                values.Item2[j] = values.Item2[j] + yC; 
+                values.Item2[j] = values.Item2[j] + yC;
             }
             return values;
         }
@@ -1458,13 +1460,13 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             }
             else // get smaller angleDifference
             {
-                if(angleDifference > Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference;  // was larger angleDifference
+                if (angleDifference > Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference;  // was larger angleDifference
             }
-            int numberOfPoints = (int) Math.Ceiling(angleDifference / angle);  //compute number of points to sample
+            int numberOfPoints = (int)Math.Ceiling(angleDifference / angle);  //compute number of points to sample
             double[] xValues = new double[numberOfPoints];
             double[] yValues = new double[numberOfPoints];
             double phiCurrent = phiStart;
-            for (int j = 0; j < numberOfPoints-1; j++) //compute intermediate points
+            for (int j = 0; j < numberOfPoints - 1; j++) //compute intermediate points
             {
                 if (clockwise) phiCurrent -= angle; //get new angle
                 else phiCurrent += angle;

+ 39 - 0
SketchAssistant/SketchAssistantWPF/Frame.cs

@@ -0,0 +1,39 @@
+namespace OptiTrack
+{
+    public class Frame
+    {
+        public Trackable[] Trackables;
+        public Marker[] Markers;
+
+        public Frame(int markerCount, int trackableCount)
+        {
+            Trackables = new Trackable[trackableCount];
+            Markers = new Marker[markerCount];
+        }
+    }
+
+    public class Trackable
+    {
+        public int Id;
+        public float X, Y, Z, Qx, Qy, Qz, Qw, Pitch, Roll, Yaw;
+        public bool IsTracked;
+
+        internal bool IsAlmostSameCoordinates(Trackable t)
+        {
+            return X == t.X && Y == t.Y && Z == t.Z;
+        }
+    }
+
+    public class Marker
+    {
+        public float X, Y, Z;
+        public Trackable BoundToTrackable;
+
+        public Marker(float x, float y, float z)
+        {
+            X = x;
+            Y = y;
+            Z = z;
+        }
+    }
+}

+ 149 - 0
SketchAssistant/SketchAssistantWPF/GeometryCalculator.cs

@@ -12,6 +12,155 @@ namespace SketchAssistantWPF
     /// </summary>
     public static class GeometryCalculator
     {
+        /// <summary>
+        /// Calculate the approximate similarity of two lines. 
+        /// Using three weighted parameters to calculate a value between 0 and 1 to indicate the similarity of the lines.
+        /// </summary>
+        /// <param name="line0">The first line.</param>
+        /// <param name="line1">The second line.</param>
+        /// <returns>The similarity of the two lines.</returns>
+        public static double CalculateSimilarity(InternalLine line0, InternalLine line1)
+        {
+            double CosSim = Math.Abs(CalculateAverageCosineSimilarity(line0, line1));
+            double LenSim = CalculateLengthSimilarity(line0, line1);
+            double AvDist = CalculateAverageDistance(line0, line1);
+            double DistSim = (50 - AvDist) / 50;
+            if (DistSim < 0) DistSim = 0;
+            if (CosSim < 0.5 || Double.IsNaN(CosSim)) CosSim = 0;
+            double output = (2 * CosSim + LenSim + DistSim) / 4;
+            System.Diagnostics.Debug.WriteLine("Results: CosSim: {0}, LenSim: {1}, AvDist {2}, DistSim: {3}. Total: {4}",
+                CosSim, LenSim, AvDist, DistSim, output);
+            return output;
+        }
+
+        /// <summary>
+        /// The cosine similarity of two vectors.
+        /// </summary>
+        /// <param name="v0">The first vector</param>
+        /// <param name="v1">The second vector</param>
+        /// <returns>The cosine similarity</returns>
+        private static double CosineSimilarity(Vector v0, Vector v1)
+        {
+            return (v0.X * v1.X + v0.Y * v1.Y) / (Math.Sqrt(v0.X * v0.X + v0.Y * v0.Y) * Math.Sqrt(v1.X * v1.X + v1.Y * v1.Y));
+        }
+
+        /// <summary>
+        /// An approximate calculation of the average cosine similarity 
+        /// of two lines, achieved by calculating the cosine similarity of subvectors.
+        /// </summary>
+        /// <param name="line0">The first line</param>
+        /// <param name="line1">The second line</param>
+        /// <returns>The approximate average cosine similarity of all subvectors</returns>
+        public static double CalculateAverageCosineSimilarity(InternalLine line0, InternalLine line1)
+        {
+            //check if one of the lines is a point, or both lines are points
+            if ((line0.isPoint && !line1.isPoint) || (line1.isPoint && !line0.isPoint)) return 0;
+            else if (line0.isPoint && line1.isPoint) return 1;
+
+            List<Point> points0 = line0.GetPoints();
+            List<Point> points1 = line1.GetPoints();
+
+            if (points0.Count == points1.Count)
+            {
+                //If the two lists have an equal amount of subvectors, 
+                //compare the nth subvectors from each list and average the value.
+                double sum = 0; int i = 0;
+                List<Point> shortL = points0; List<Point> longL = points1;
+                for (; i < shortL.Count - 1; i++)
+                {
+                    if (i + 1 == shortL.Count || i + 1 == longL.Count) break;
+                    Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
+                    Vector v1 = new Vector(longL[i + 1].X - longL[i].X, longL[i + 1].Y - longL[i].Y);
+                    sum += CosineSimilarity(v0, v1);
+                }
+                return sum / i;
+            }
+            else
+            {
+                //Determine if the longer list is of similar length or contains significatly more items
+                List<Point> shortL = points0; List<Point> longL = points0;
+                if (points0.Count < points1.Count) { longL = points1; }
+                if (points0.Count > points1.Count) { shortL = points1; }
+                double dif = 2;
+                if (shortL.Count != 1)
+                {
+                    dif = (longL.Count - 1) / (shortL.Count - 1);
+                }
+                
+                if (dif > 1)
+                {
+                    //The longer list is significantly longer
+                    //Each element in the shorter list is compared to multiple 
+                    // elements in the longer list to make up the difference
+                    double sum = 0; int adds = 0;
+
+                    for (int i = 0; i < shortL.Count - 1; i++)
+                    {
+                        if (i + 1 == shortL.Count) break;
+                        for (int j = 0; j <= dif; j++)
+                        {
+                            var k = i + j;
+                            if (k + 1 == longL.Count) break;
+                            Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
+                            Vector v1 = new Vector(longL[k + 1].X - longL[k].X, longL[k + 1].Y - longL[k].Y);
+                            sum += CosineSimilarity(v0, v1); adds++;
+                        }
+                    }
+                    return sum / adds;
+                }
+                else
+                {
+                    //The longer list is almost the same length as the shorter list
+                    //The remaining items are simply skipped
+                    double sum = 0; int i = 0;
+                    for (; i < shortL.Count - 1; i++)
+                    {
+                        if (i + 1 == shortL.Count || i + 1 == longL.Count) break;
+                        Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
+                        Vector v1 = new Vector(longL[i + 1].X - longL[i].X, longL[i + 1].Y - longL[i].Y);
+                        sum += CosineSimilarity(v0, v1);
+                    }
+                    return sum / i;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Calculate the similarity in length of two Lines.
+        /// </summary>
+        /// <param name="line0">The first line.</param>
+        /// <param name="line1">The second line.</param>
+        /// <returns>How similar the lines are in length.</returns>
+        private static double CalculateLengthSimilarity(InternalLine line0, InternalLine line1)
+        {
+            double len0 = line0.GetLength(); double len1 = line1.GetLength();
+            var dif = Math.Abs(len1 - len0);
+            if (dif == 0) return 1;
+            double shorter;
+            if (len1 > len0) shorter = len0;
+            else shorter = len1;
+            if (dif >= shorter) return 0;
+            return (shorter - dif) / shorter;
+        }
+
+        /// <summary>
+        /// Calculate the average distance between the ends of two lines.
+        /// </summary>
+        /// <param name="line0">The first line.</param>
+        /// <param name="line1">The second line.</param>
+        /// <returns>The shortest average distance between the ends of the lines.</returns>
+        private static double CalculateAverageDistance(InternalLine line0, InternalLine line1)
+        {
+            List<Point> points0 = line0.GetPoints();
+            List<Point> points1 = line1.GetPoints();
+            double distfirstfirst = Math.Sqrt(Math.Pow((points0[0].X - points1[0].X), 2) + Math.Pow((points0[0].Y - points1[0].Y), 2));
+            double distlastlast = Math.Sqrt(Math.Pow((points0.Last().X - points1.Last().X), 2) + Math.Pow((points0.Last().Y - points1.Last().Y), 2));
+            double distfirstlast = Math.Sqrt(Math.Pow((points0[0].X - points1.Last().X), 2) + Math.Pow((points0[0].Y - points1.Last().Y), 2));
+            double distlastfirst = Math.Sqrt(Math.Pow((points0.Last().X - points1[0].X), 2) + Math.Pow((points0.Last().Y - points1[0].Y), 2));
+            if ((distfirstfirst + distlastlast) / 2 < (distfirstlast + distlastfirst) / 2) return (distfirstfirst + distlastlast) / 2;
+            else return (distfirstlast + distlastfirst) / 2;
+        }
+
         /// <summary>
         /// A simple algorithm that returns a filled circle with a radius and a center point.
         /// </summary>

+ 39 - 0
SketchAssistant/SketchAssistantWPF/HighPerformanceTimer.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OptiTrack
+{
+    public class HighPerformanceTimer
+    {
+        [DllImport("Kernel32.dll")]
+        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
+
+        [DllImport("Kernel32.dll")]
+        private static extern bool QueryPerformanceFrequency(out long lpFrequency);
+
+        private long start;
+        private long freq;
+
+        public HighPerformanceTimer()
+        {
+            start = 0;
+            QueryPerformanceFrequency(out freq);
+        }
+
+        public void Start()
+        {
+            QueryPerformanceCounter(out start);
+        }
+
+        public double Stop()
+        {
+            long stop;
+            QueryPerformanceCounter(out stop);
+            return (double)(stop - start) / (double)freq;
+        }
+    }
+}

+ 43 - 14
SketchAssistant/SketchAssistantWPF/InternalLine.cs

@@ -34,6 +34,10 @@ namespace SketchAssistantWPF
         /// The location of the point, if this is a point
         /// </summary>
         public Point point { get; private set; }
+        /// <summary>
+        /// The length of the line
+        /// </summary>
+        private double length = -1;
 
         /// <summary>
         /// The constructor for lines which are only temporary.
@@ -62,14 +66,18 @@ namespace SketchAssistantWPF
             isTemporary = false;
         }
 
-        public Point GetStartPoint()
-        {
-            return linePoints.First();
-        }
-
-        public Point GetEndPoint()
+        /// <summary>
+        /// A function to make temporary lines non-temporary.
+        /// </summary>
+        /// <param name="id">The id of the line.</param>
+        public void MakePermanent(int id)
         {
-            return linePoints.Last();
+            if (isTemporary)
+            {
+                identifier = id;
+                CleanPoints();
+                isTemporary = false;
+            }
         }
 
         public List<Point> GetPoints()
@@ -87,6 +95,26 @@ namespace SketchAssistantWPF
             return pointColl;
         }
 
+
+        /// <summary>
+        /// Get the length of the line.
+        /// </summary>
+        /// <returns>The length of the line.</returns>
+        public double GetLength()
+        {
+            if (length < 0)
+            {
+                length = 0;
+                for (int i = 0; i < linePoints.Count - 1; i++)
+                {
+                    var a = linePoints[i]; var b = linePoints[i + 1];
+
+                    length += Math.Sqrt(Math.Pow((a.X - b.X), 2) + Math.Pow((a.Y - b.Y), 2));
+                }
+            }
+            return length;
+        }
+
         /// <summary>
         /// A function that will take two matrixes and populate them with the line data of this line object
         /// </summary>
@@ -101,12 +129,12 @@ namespace SketchAssistantWPF
                     if (currPoint.X >= 0 && currPoint.Y >= 0 &&
                         currPoint.X < boolMatrix.GetLength(0) && currPoint.Y < boolMatrix.GetLength(1))
                     {
-                        boolMatrix[(int) currPoint.X, (int) currPoint.Y] = true;
-                        if (listMatrix[(int) currPoint.X, (int) currPoint.Y] == null)
+                        boolMatrix[(int)currPoint.X, (int)currPoint.Y] = true;
+                        if (listMatrix[(int)currPoint.X, (int)currPoint.Y] == null)
                         {
-                            listMatrix[(int) currPoint.X, (int) currPoint.Y] = new HashSet<int>();
+                            listMatrix[(int)currPoint.X, (int)currPoint.Y] = new HashSet<int>();
                         }
-                        listMatrix[(int) currPoint.X, (int) currPoint.Y].Add(identifier);
+                        listMatrix[(int)currPoint.X, (int)currPoint.Y].Add(identifier);
                     }
                 }
             }
@@ -121,16 +149,17 @@ namespace SketchAssistantWPF
             {
                 //check if its a point
                 var localIsPoint = linePoints.All(o => o.X == linePoints.First().X && o.Y == linePoints.First().Y);
-                if (!localIsPoint) {
+                if (!localIsPoint)
+                {
                     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 = (int) linePoints[0].X + 1;
+                    int nullValue = (int)linePoints[0].X + 1;
                     //Fill the gaps between points
                     for (int i = 0; i < linePoints.Count - 1; i++)
                     {
-                        nullValue += (int) linePoints[i + 1].X;
+                        nullValue += (int)linePoints[i + 1].X;
                         List<Point> partialList = GeometryCalculator.BresenhamLineAlgorithm(linePoints[i], linePoints[i + 1]);
                         tempList.AddRange(partialList);
                     }

+ 97 - 6
SketchAssistant/SketchAssistantWPF/LocalArmbandInterface.cs

@@ -7,22 +7,113 @@ using System.Runtime.InteropServices;
 
 namespace SketchAssistantWPF
 {
-    class LocalArmbandInterface
+    /// <summary>
+    /// interface providing access to the vibrotactile armband
+    /// accessing the BodyActuator.dll C library via the StaticLibMotors.dll c++ library
+    /// </summary>
+    public static class LocalArmbandInterface
     {
+        /// <summary>
+        /// constant to set the vibration mode:
+        /// 0 -> no vibration
+        /// 1 -> intensity 0.33
+        /// 2 -> intensity 0.66
+        /// 3 -> intensity 1.0
+        /// </summary>
+        static readonly int VIBRATION_MODE = 2;
 
+
+        /// <summary>
+        /// initializes the armband (and binds the C dll)
+        /// must be called before calling any other of the methods of this class
+        /// explicitly allocates memory and therefore must only be called once and must be followed by a call to DestroyArmband eventually
+        /// </summary>
+        /// <returns>an integer purely for debugging purposes, -1 means no unexpected behaviour occured and the initialization was successful</returns>
         [DllImport (@"../Debug/StaticLibMotors.dll", EntryPoint = "?setupArmband@ArmbandInterface@@QAAHXZ",
      CallingConvention = CallingConvention.Cdecl)]
-        public static extern int setupArmband();
+        public static extern int SetupArmband();
+
+        /// <summary>
+        /// destroys the armband instance created by SetupArmband (thus freeing its allocated memory)
+        /// </summary>
+        /// <returns></returns>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?destroyArmband@ArmbandInterface@@QAAHXZ",
+     CallingConvention = CallingConvention.Cdecl)]
+        public static extern int DestroyArmband();
 
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at the specified intensity (until it is stopped)
+        /// </summary>
+        /// <param name="motorNumber">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
         [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?startVibrate@ArmbandInterface@@QAAXHM@Z",
      CallingConvention = CallingConvention.Cdecl)]
-        public static extern void startVibrate(int motorNumber, float intensity);
+        public static extern void StartVibrate(int motorNumber, double intensity);
+
+        /// <summary>
+        /// stop actuation of the specified tactor (motor)
+        /// </summary>
+        /// <param name="motorNumber">integer from 0 to 7 specifying the number of the tactor to stop</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?stopVibrate@ArmbandInterface@@QAAXH@Z",                       
+     CallingConvention = CallingConvention.Cdecl)]
+        public static extern void StopVibration(int motorNumber);
+
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at  0.33 for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?actuate33@ArmbandInterface@@QAAXHNH@Z",
+     CallingConvention = CallingConvention.Cdecl)]
+        private static extern void Actuate33(int tactor, double intensity, int duration);
+
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at intensity 0.66 for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?actuate66@ArmbandInterface@@QAAXHNH@Z",
+     CallingConvention = CallingConvention.Cdecl)]
+        private static extern void Actuate66(int tactor, double intensity, int duration);
 
-        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?stopVibrate@ArmbandInterface@@QAAXH@Z",
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at intensity 1.0 for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?actuate100@ArmbandInterface@@QAAXHNH@Z",
      CallingConvention = CallingConvention.Cdecl)]
-        public static extern void stopVibration(int motorNumber);
+        private static extern void Actuate100(int tactor, double intensity, int duration);
 
-        //public void Vibrate()
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at the specified intensity (specified in VIBRATION_MODE) for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        public static void Actuate(int tactor, double intensity, int duration)
+        {
+            switch (VIBRATION_MODE)
+            {
+                case 0:
+                    break;
+                case 1:
+                    Actuate33(tactor, intensity, duration);
+                    break;
+                case 2:
+                    Actuate66(tactor, intensity, duration);
+                    break;
+                case 3:
+                    Actuate100(tactor, intensity, duration);
+                    break;
+                default:
+                    Console.WriteLine("Error: invalid value for VIBRATION_MODE constant");
+                    break;
+            }
+        }
 
     }
 }

+ 404 - 103
SketchAssistant/SketchAssistantWPF/MVP_Model.cs

@@ -7,6 +7,10 @@ using System.Threading.Tasks;
 using System.Windows.Controls;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
+using OptiTrack;
+using System.Runtime.InteropServices;
+using System.IO;
+using System.IO.Ports;
 
 namespace SketchAssistantWPF
 {
@@ -21,27 +25,40 @@ namespace SketchAssistantWPF
         /// </summary>
         ActionHistory historyOfActions;
         /// <summary>
-        /// The assistant responsible for the redraw mode
+        /// The connector class used to get frames from the Optitrack system.
         /// </summary>
-        //RedrawAssistant redrawAss;
+        OptiTrackConnector connector;
 
 
+        /// <summary>
+        /// intensity of the tectile feedback occuring once every passed centimeter (ranging from 0.0 to 1.0)
+        /// </summary>
+        static readonly double TACTILE_SURFACE_FEEDBACK_INTENSITY = 1.0;
+        /// <summary>
+        /// duration of the tectile feedback occuring once every passed centimeter (in milliseconds)
+        /// </summary>
+        static readonly int TACTILE_SURFACE_FEEDBACK_DURATION = 15;
+
         /***********************/
         /*** CLASS VARIABLES ***/
         /***********************/
 
+        /// <summary>
+        /// this is a variable used for detecting whether the tracker is in the warning zone (0 +- variable), no drawing zone (0 +- 2 * variable) or normal drawing zone
+        /// </summary>
+        readonly double WARNING_ZONE_BOUNDARY = 0.10; //10cm
         /// <summary>
         /// If the program is in drawing mode.
         /// </summary>
-        bool inDrawingMode;
+        public bool inDrawingMode { get; private set; }
         /// <summary>
-        /// Size of deletion area
+        /// if the program is using OptiTrack
         /// </summary>
-        int deletionRadius = 5;
+        public bool optiTrackInUse { get; private set; }
         /// <summary>
-        /// Size of areas marking endpoints of lines in the redraw mode.
+        /// Size of deletion area
         /// </summary>
-        int markerRadius = 10;
+        int deletionRadius = 5;
         /// <summary>
         /// The Position of the Cursor in the right picture box
         /// </summary>
@@ -55,6 +72,18 @@ namespace SketchAssistantWPF
         /// </summary>
         Queue<Point> cursorPositions = new Queue<Point>();
         /// <summary>
+        /// The Position of the Cursor of opti track
+        /// </summary>
+        Point currentOptiCursorPosition;
+        /// <summary>
+        /// The Previous Cursor Position of opti track
+        /// </summary>
+        Point previousOptiCursorPosition;
+        /// <summary>
+        /// Queue for the cursorPositions of opti track
+        /// </summary>
+        Queue<Point> optiCursorPositions = new Queue<Point>();
+        /// <summary>
         /// Lookup Matrix for checking postions of lines in the image
         /// </summary>
         bool[,] isFilledMatrix;
@@ -63,10 +92,6 @@ namespace SketchAssistantWPF
         /// </summary>
         HashSet<int>[,] linesMatrix;
         /// <summary>
-        /// List of items which will be overlayed over the right canvas.
-        /// </summary>
-        List<Tuple<bool, HashSet<Point>>> overlayItems;
-        /// <summary>
         /// Width of the LeftImageBox.
         /// </summary>
         public int leftImageBoxWidth;
@@ -82,48 +107,113 @@ namespace SketchAssistantWPF
         /// Height of the RightImageBox.
         /// </summary>
         public int rightImageBoxHeight;
-
-        public ImageDimension leftImageSize { get; private set; }
-
+        /// <summary>
+        /// The size of the right canvas.
+        /// </summary>
         public ImageDimension rightImageSize { get; private set; }
         /// <summary>
         /// Indicates whether or not the canvas on the right side is active.
         /// </summary>
-        public bool canvasActive {get; set;}
+        public bool canvasActive { get; set; }
         /// <summary>
         /// Indicates if there is a graphic loaded in the left canvas.
         /// </summary>
         public bool graphicLoaded { get; set; }
         /// <summary>
+        /// Whether or not an optitrack system is avaiable.
+        /// </summary>
+        public bool optitrackAvailable { get; private set; }
+        /// <summary>
+        /// x coordinate in real world. one unit is one meter. If standing in front of video wall facing it, moving left results in incrementation of x.
+        /// </summary>
+        public float optiTrackX;
+        /// <summary>
+        /// y coordinate in real world. one unit is one meter. If standing in front of video wall, moving up results in incrementation of y.
+        /// </summary>
+        public float optiTrackY;
+        /// <summary>
+        /// z coordinate in real world. one unit is one meter. If standing in front of video wall, moving back results in incrementation of y.
+        /// </summary>
+        public float optiTrackZ;
+        /// <summary>
+        /// keeps track of whether last tick the trackable was inside drawing zone or not.
+        /// </summary>
+        private bool optiTrackInsideDrawingZone = false;
+        /// <summary>
+        /// object of class wristband used for controlling the vibrotactile wristband
+        /// </summary>
+        private Wristband wristband;
+        /// <summary>
+        /// Is set to true when the trackable has passed to the backside of the drawing surface, 
+        /// invalidating all inputs on its way back.
+        /// </summary>
+        bool OptiMovingBack = false;
+        /// <summary>
+        /// The Layer in which the optitrack system was. 0 is drawing layer, -1 is in front, 1 is behind
+        /// </summary>
+        int OptiLayer = 0;
+        /// <summary>
+        /// The path traveled since the last tick
+        /// </summary>
+        double PathTraveled = 0;
+        /// <summary>
         /// Whether or not the mouse is pressed.
         /// </summary>
         private bool mouseDown;
-
-        Image rightImageWithoutOverlay;
-
+        /// <summary>
+        /// A List of lines in the left canvas.
+        /// </summary>
         List<InternalLine> leftLineList;
-        
+        /// <summary>
+        /// A list of lines in the right canvas along with a boolean indicating if they should be drawn.
+        /// </summary>
         List<Tuple<bool, InternalLine>> rightLineList;
-
+        /// <summary>
+        /// The line currently being drawin with optitrack.
+        /// </summary>
         List<Point> currentLine = new List<Point>();
-
-
+        /// <summary>
+        /// If the COM5 port is available.
+        /// </summary>
+        bool comFiveAvailable = false;
 
         public MVP_Model(MVP_Presenter presenter)
         {
             //TODO remove
-            Console.WriteLine("trying to initialize Armband...");
-            int tmp= LocalArmbandInterface.setupArmband();
-            Console.WriteLine("Armband initialization terminated, exit code: " + tmp);
+            var portName = "COM5";
+
+            var isValid = SerialPort.GetPortNames().Any(x => string.Compare(x, portName, true) == 0);
+            if (isValid)
+            {
+                Console.WriteLine("trying to initialize Armband...");
+                int tmp = LocalArmbandInterface.SetupArmband();
+                Console.WriteLine("Armband initialization terminated, exit code: " + tmp);
+                comFiveAvailable = true;
+            }
+            else
+            {
+                Console.WriteLine("COM5 device not found");
+            }
+
             programPresenter = presenter;
             historyOfActions = new ActionHistory();
-            //redrawAss = new RedrawAssistant();
-            //overlayItems = new List<Tuple<bool, HashSet<Point>>>();
             rightLineList = new List<Tuple<bool, InternalLine>>();
             canvasActive = false;
             UpdateUI();
             rightImageSize = new ImageDimension(0, 0);
-            leftImageSize = new ImageDimension(0, 0);
+            connector = new OptiTrackConnector();
+            wristband = new Wristband();
+
+            //Set up Optitrack
+            optitrackAvailable = false;
+            if (File.Exists(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
+            {
+                if (connector.Init(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
+                {
+                    optitrackAvailable = true;
+                    connector.StartTracking(GetOptiTrackPosition);
+                }
+            }
         }
 
         /**************************/
@@ -131,6 +221,31 @@ namespace SketchAssistantWPF
         /**************************/
 
 
+        /// <summary>
+        /// Check if the Optitrack trackable is in the drawing plane.
+        /// </summary>
+        /// <param name="optiTrackZ">The real world z coordinates of the trackable.</param>
+        /// <returns>If the trackable is in front of the drawing plane</returns>
+        private bool CheckInsideDrawingZone(float optiTrackZ)
+        {
+            if (Math.Abs(optiTrackZ) > WARNING_ZONE_BOUNDARY * 2) return false;
+            return true;
+        }
+
+        /// <summary>
+        /// Function that is called by the OptitrackController to pass frames to the model.
+        /// </summary>
+        /// <param name="frame">An Optitrack Frame</param>
+        void GetOptiTrackPosition(OptiTrack.Frame frame)
+        {
+            if (frame.Trackables.Length >= 1)
+            {
+                optiTrackX = frame.Trackables[0].X;
+                optiTrackY = frame.Trackables[0].Y;
+                optiTrackZ = frame.Trackables[0].Z;
+            }
+        }
+
         /// <summary>
         /// Change the status of whether or not the lines are shown.
         /// </summary>
@@ -147,6 +262,24 @@ namespace SketchAssistantWPF
             }
         }
 
+
+        /// <summary>
+        /// Check if enough distance has been travelled to warrant a vibration.
+        /// </summary>
+        private void CheckPathTraveled()
+        {
+            var a = Math.Abs(previousOptiCursorPosition.X - currentOptiCursorPosition.X);
+            var b = Math.Abs(previousOptiCursorPosition.Y - currentOptiCursorPosition.Y);
+            PathTraveled += Math.Sqrt(Math.Pow(a,2) + Math.Pow(b,2));
+            //Set the Interval of vibrations here
+            if(PathTraveled > 15)
+            {
+                PathTraveled = 0;
+                if(comFiveAvailable)
+                    LocalArmbandInterface.Actuate(0, TACTILE_SURFACE_FEEDBACK_INTENSITY, TACTILE_SURFACE_FEEDBACK_DURATION);
+            }
+        }
+
         /// <summary>
         /// A function that populates the matrixes needed for deletion detection with line data.
         /// </summary>
@@ -171,7 +304,7 @@ namespace SketchAssistantWPF
         /// </summary>
         private void UpdateUI()
         {
-            programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded);
+            programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded, optitrackAvailable, optiTrackInUse);
         }
 
         /// <summary>
@@ -198,6 +331,60 @@ namespace SketchAssistantWPF
             return returnSet;
         }
 
+        /// <summary>
+        /// Converts given point to device-independent pixel.
+        /// </summary>
+        /// <param name="p">real world coordinate</param>
+        /// <returns>The given Point converted to device-independent pixel </returns>
+        private Point ConvertTo96thsOfInch(Point p)
+        {
+            //The position of the optitrack coordinate system
+            // and sizes of the optitrack tracking area relative to the canvas.
+            double OPTITRACK_X_OFFSET = -0.4854;
+            double OPTITRACK_Y_OFFSET = 0.9;
+            double OPTITRACK_CANVAS_HEIGHT = 1.2;
+            double OPTITRACK_X_SCALE = 0.21 * (((1.8316/*size of canvas*/ / 0.0254) * 96) / (1.8316));
+            double OPTITRACK_Y_SCALE = 0.205 * (((1.2 / 0.0254) * 96) / (1.2));
+            //The coordinates on the display
+            double xCoordinate = (p.X - OPTITRACK_X_OFFSET) * OPTITRACK_X_SCALE;
+            double yCoordinate = (OPTITRACK_CANVAS_HEIGHT - (p.Y - OPTITRACK_Y_OFFSET)) * OPTITRACK_Y_SCALE;
+            return new Point(xCoordinate, yCoordinate);
+        }
+
+        /// <summary>
+        /// Updates the Optitrack coordiantes, aswell as the marker for optitrack.
+        /// </summary>
+        /// <param name="p">The new cursor position</param>
+        private void SetCurrentFingerPosition(Point p)
+        {
+            Point correctedPoint = ConvertTo96thsOfInch(p);
+
+
+            if (optiTrackZ < -2.2 * WARNING_ZONE_BOUNDARY && OptiLayer > -1)
+            {
+                OptiLayer = -1; OptiMovingBack = false;
+                programPresenter.SetOverlayColor("optipoint", Brushes.Yellow);
+            }
+            else if (optiTrackZ > 2 * WARNING_ZONE_BOUNDARY && OptiLayer < 1)
+            {
+                programPresenter.SetOverlayColor("optipoint", Brushes.Red);
+                OptiLayer = 1;
+            }
+            else if(optiTrackZ <= 2 * WARNING_ZONE_BOUNDARY && optiTrackZ >= -2.2 * WARNING_ZONE_BOUNDARY){
+                if(OptiLayer > 0)
+                    OptiMovingBack = true;
+                programPresenter.SetOverlayColor("optipoint", Brushes.Green);
+                OptiLayer = 0;
+            }
+
+            currentOptiCursorPosition = correctedPoint;
+            programPresenter.MoveOptiPoint(currentOptiCursorPosition);
+
+            if (optiCursorPositions.Count > 0) { previousOptiCursorPosition = optiCursorPositions.Dequeue(); }
+            else { previousOptiCursorPosition = currentOptiCursorPosition; }
+            optiCursorPositions.Enqueue(currentOptiCursorPosition);
+        }
+
         /********************************************/
         /*** FUNCTIONS TO INTERACT WITH PRESENTER ***/
         /********************************************/
@@ -205,13 +392,10 @@ namespace SketchAssistantWPF
         /// <summary>
         /// A function to update the dimensions of the left and right canvas when the window is resized.
         /// </summary>
-        /// <param name="LeftCanvas">The size of the left canvas.</param>
         /// <param name="RightCanvas">The size of the right canvas.</param>
-        public void ResizeEvent(ImageDimension LeftCanvas, ImageDimension RightCanvas)
+        public void ResizeEvent(ImageDimension RightCanvas)
         {
-            if(LeftCanvas.Height >= 0 && LeftCanvas.Width>= 0) { leftImageSize = LeftCanvas; }
-            if(RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
-          
+            if (RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
             RepopulateDeletionMatrixes();
         }
 
@@ -220,6 +404,8 @@ namespace SketchAssistantWPF
         /// </summary>
         public void ResetRightImage()
         {
+            if(currentLine.Count > 0)
+                FinishCurrentLine(true);
             rightLineList.Clear();
             programPresenter.PassLastActionTaken(historyOfActions.Reset());
             programPresenter.ClearRightLines();
@@ -233,30 +419,11 @@ namespace SketchAssistantWPF
         /// <param name="listOfLines">The List of Lines to be displayed in the left image.</param>
         public void SetLeftLineList(int width, int height, List<InternalLine> listOfLines)
         {
-            leftImageSize = new ImageDimension(width, height);
             rightImageSize = new ImageDimension(width, height);
             leftLineList = listOfLines;
             graphicLoaded = true;
             programPresenter.UpdateLeftLines(leftLineList);
-            //programPresenter.ClearRightLines(); //TODO check if right position for this method call
             CanvasActivated();
-            /*
-            var workingCanvas = GetEmptyCanvas(width, height);
-            var workingGraph = Graphics.FromImage(workingCanvas);
-            leftLineList = listOfLines;
-            //redrawAss = new RedrawAssistant(leftLineList);
-            //overlayItems = redrawAss.Initialize(markerRadius);
-            //Lines
-            foreach (InternalLine line in leftLineList)
-            {
-                line.DrawLine(workingGraph);
-            }
-            leftImage = workingCanvas;
-            programPresenter.UpdateLeftImage(leftImage);
-            //Set right image to same size as left image and delete linelist
-            DrawEmptyCanvasRight();
-            rightLineList = new List<Tuple<bool, InternalLine>>();
-            */
         }
 
         /// <summary>
@@ -291,7 +458,6 @@ namespace SketchAssistantWPF
                     default:
                         break;
                 }
-                //TODO: For the person implementing overlay: Add check if overlay needs to be added
                 programPresenter.UpdateRightLines(rightLineList);
             }
             RepopulateDeletionMatrixes();
@@ -322,7 +488,6 @@ namespace SketchAssistantWPF
                     default:
                         break;
                 }
-                //TODO: For the person implementing overlay: Add check if overlay needs to be added
                 programPresenter.UpdateRightLines(rightLineList);
                 RepopulateDeletionMatrixes();
             }
@@ -335,10 +500,33 @@ namespace SketchAssistantWPF
         /// <param name="nowDrawing">The new drawingstate of the program</param>
         public void ChangeState(bool nowDrawing)
         {
+            if(inDrawingMode && !nowDrawing && currentLine.Count > 0 && optiTrackInUse)
+                FinishCurrentLine(true);
             inDrawingMode = nowDrawing;
             UpdateUI();
         }
-        
+
+        /// <summary>
+        /// The function called by the Presenter to set a variable which describes if OptiTrack is in use
+        /// </summary>
+        /// <param name="usingOptiTrack">The status of optitrack button</param>
+        public void SetOptiTrack(bool usingOptiTrack)
+        {
+            optiTrackInUse = usingOptiTrack;
+            if (usingOptiTrack && optiTrackX == 0 && optiTrackY == 0 && optiTrackZ == 0)
+            {
+                programPresenter.PassWarning("Trackable not detected, please check if OptiTrack is activated and Trackable is recognized");
+                optiTrackInUse = false;
+                //Disable optipoint
+                programPresenter.SetOverlayStatus("optipoint", false, currentCursorPosition);
+            }
+            else
+            {
+                //Enable optipoint
+                programPresenter.SetOverlayStatus("optipoint", true, currentCursorPosition);
+            }
+        }
+
         /// <summary>
         /// Updates the current cursor position of the model.
         /// </summary>
@@ -352,24 +540,31 @@ namespace SketchAssistantWPF
         /// <summary>
         /// Start a new Line, when the Mouse is pressed down.
         /// </summary>
-        public void MouseDown()
+        public void StartNewLine()
         {
             mouseDown = true;
-            if (inDrawingMode && mouseDown)
+            if (inDrawingMode)
             {
-                currentLine.Clear();
-                currentLine.Add(currentCursorPosition);
+                if(optiTrackInUse)
+                {
+                    currentLine.Clear();
+                    currentLine.Add(currentOptiCursorPosition);
+                }
+                else if (programPresenter.IsMousePressed())
+                {
+                    currentLine.Clear();
+                    currentLine.Add(currentCursorPosition);
+                }
             }
-            //TODO remove
-            LocalArmbandInterface.startVibrate(0, 1);
         }
 
         /// <summary>
         /// Finish the current Line, when the pressed Mouse is released.
         /// </summary>
         /// <param name="valid">Whether the up event is valid or not</param>
-        public void MouseUp(bool valid)
+        public void FinishCurrentLine(bool valid)
         {
+
             mouseDown = false;
             if (valid)
             {
@@ -382,7 +577,7 @@ namespace SketchAssistantWPF
                     //TODO: For the person implementing overlay: Add check if overlay needs to be added
                     programPresenter.UpdateRightLines(rightLineList);
                     currentLine.Clear();
-                    //programPresenter.UpdateCurrentLine(currentLine);
+                    programPresenter.UpdateCurrentLine(currentLine);
                 }
             }
             else
@@ -390,8 +585,26 @@ namespace SketchAssistantWPF
                 currentLine.Clear();
             }
             UpdateUI();
-            //TODO remove
-            LocalArmbandInterface.stopVibration(0);
+        }
+
+        /// <summary>
+        /// Finish the current Line, when the pressed Mouse is released.
+        /// Overload that is used to pass a list of points to be used when one is available.
+        /// </summary>
+        /// <param name="p">The list of points</param>
+        public void FinishCurrentLine(List<Point> p)
+        {
+            mouseDown = false;
+            if (inDrawingMode && currentLine.Count > 0)
+            {
+                InternalLine newLine = new InternalLine(p, rightLineList.Count);
+                rightLineList.Add(new Tuple<bool, InternalLine>(true, newLine));
+                newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
+                programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID())));
+                programPresenter.UpdateRightLines(rightLineList);
+                currentLine.Clear();
+            }
+            UpdateUI();
         }
 
         /// <summary>
@@ -399,19 +612,83 @@ namespace SketchAssistantWPF
         /// </summary>
         public void Tick()
         {
+            //Show startpoints
+            if (leftLineList != null && leftLineList.Count > 0)
+            {
+                var templist = rightLineList.Where(tup => tup.Item1).ToList();
+                if (templist.Count < leftLineList.Count)
+                {
+                    programPresenter.SetOverlayStatus("startpoint", true, leftLineList[templist.Count].GetPoints()[0]);
+                }
+                else
+                {
+                    programPresenter.SetOverlayStatus("startpoint", false, new Point(50, 50));
+                }
+
+            }
+
             if (cursorPositions.Count > 0) { previousCursorPosition = cursorPositions.Dequeue(); }
             else { previousCursorPosition = currentCursorPosition; }
-            cursorPositions.Enqueue(currentCursorPosition);
-            //Drawing
-            if (inDrawingMode && programPresenter.IsMousePressed())
+
+            if(optitrackAvailable)
+                SetCurrentFingerPosition(new Point(optiTrackX, optiTrackY));
+            
+
+            if (optiTrackInUse && inDrawingMode && !OptiMovingBack) // optitrack is being used
+            {
+                //outside of drawing zone
+                if (!CheckInsideDrawingZone(optiTrackZ))
+                {
+                    //Check if trackable was in drawing zone last tick & program is in drawing mode-> finish line
+                    if (optiTrackInsideDrawingZone && inDrawingMode) 
+                    {
+                        optiTrackInsideDrawingZone = false;
+                        FinishCurrentLine(true);
+                    }
+                }
+                else //Draw with optitrack, when in drawing zone
+                {
+                    //Optitrack wasn't in the drawing zone last tick -> start a new line
+                    if (!optiTrackInsideDrawingZone)
+                    {
+                        optiTrackInsideDrawingZone = true;
+                        StartNewLine();
+                    }
+                    else currentLine.Add(currentOptiCursorPosition);
+                    programPresenter.UpdateCurrentLine(currentLine);
+                    if (optiTrackZ > WARNING_ZONE_BOUNDARY)
+                    {
+                        wristband.PushForward();
+                    }
+                    else if (optiTrackZ < -1 * WARNING_ZONE_BOUNDARY)
+                    {
+                        wristband.PushBackward();
+                    }
+
+                    CheckPathTraveled();
+                }
+            }
+            else if( !optiTrackInUse && inDrawingMode)
             {
-                currentLine.Add(currentCursorPosition);
-                //programPresenter.UpdateCurrentLine(currentLine);
+                //drawing without optitrack
+                cursorPositions.Enqueue(currentCursorPosition);
+                if (inDrawingMode && programPresenter.IsMousePressed())
+                {
+                    currentLine.Add(currentCursorPosition);
+                    //programPresenter.UpdateCurrentLine(currentLine);
+                }
+
             }
-            //Deleting
-            if (!inDrawingMode && programPresenter.IsMousePressed())
+
+            //Deletion mode for optitrack and regular use
+            if (!inDrawingMode)
             {
-                List<Point> uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
+                List<Point> uncheckedPoints = new List<Point>();
+                if (programPresenter.IsMousePressed() && !optiTrackInUse) //without optitrack
+                    uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
+                if(optiTrackInUse && CheckInsideDrawingZone(optiTrackZ)  && !OptiMovingBack) //with optitrack
+                    uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousOptiCursorPosition, currentOptiCursorPosition);
+
                 foreach (Point currPoint in uncheckedPoints)
                 {
                     HashSet<int> linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionRadius);
@@ -423,44 +700,68 @@ namespace SketchAssistantWPF
                             rightLineList[lineID] = new Tuple<bool, InternalLine>(false, rightLineList[lineID].Item2);
                         }
                         RepopulateDeletionMatrixes();
-                        //TODO: For the person implementing overlay: Add check if overlay needs to be added
                         programPresenter.UpdateRightLines(rightLineList);
                     }
                 }
             }
-        }
-        /*
-        /// <summary>
-        /// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
-        /// </summary>
-        /// <param name="CanvasSize">The size of the canvas</param>
-        public void UpdateSizes(ImageDimension CanvasSize)
-        {
-            if (rightImageWithoutOverlay != null)
+            /*
+            if (programPresenter.IsMousePressed()) //TODO only use if optitrack is in use (currently only available on different branch)
             {
-                int widthImage = rightImageSize.Width;
-                int heightImage = rightImageSize.Height;
-                int widthBox = CanvasSize.Width;
-                int heightBox = CanvasSize.Height;
-
-                float imageRatio = (float)widthImage / (float)heightImage;
-                float containerRatio = (float)widthBox / (float)heightBox;
-                float zoomFactor = 0;
-                if (imageRatio >= containerRatio)
+                if(CentimeterGridPassed(previousCursorPosition, currentCursorPosition)) //TODO replace with optitrack coordinates (and introduce a buffer for previous real-world coordinates)
                 {
-                    //Image is wider than it is high
-                    zoomFactor = (float)widthImage / (float)widthBox;
+                    LocalArmbandInterface.Actuate(0, TACTILE_SURFACE_FEEDBACK_INTENSITY, TACTILE_SURFACE_FEEDBACK_DURATION);
                 }
-                else
-                {
-                    //Image is higher than it is wide
-                    zoomFactor = (float)heightImage / (float)heightBox;
-                }
-                markerRadius = (int)(10 * zoomFactor);
-                deletionRadius = (int)(5 * zoomFactor);
             }
+             */
+        }
+
+        /// <summary>
+        /// checks if the centimeter grid has been passed since the last tick and a vibrotactile feedback has to be sent to the user
+        /// a high enough tick rate must be ensured to provide a useful feedback and avoid skipping necessary feedback
+        /// </summary>
+        /// <param name="previousCursorPosition">the curser position in real world coordinates (obtained from Optitrack) at the last tick</param>
+        /// <param name="currentCursorPosition">the curser position in real world coordinates (obtained from Optitrack) at the current tick</param>
+        /// <returns>true iff the grid has been passed</returns>
+        private bool CentimeterGridPassed(Point previousCursorPositionRealWorldCoordinates, Point currentCursorPositionRealWorldCoordinates)
+        {
+            //truncate coordiates to int after converting to centimeters (from meters), 
+            if ((int)(previousCursorPositionRealWorldCoordinates.X * 100) != (int)(currentCursorPositionRealWorldCoordinates.X * 100)) return true;
+            if ((int)(previousCursorPositionRealWorldCoordinates.Y * 100) != (int)(currentCursorPositionRealWorldCoordinates.Y * 100)) return true;
+            return false;
         }
-        */
+
+        /*
+/// <summary>
+/// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
+/// </summary>
+/// <param name="CanvasSize">The size of the canvas</param>
+public void UpdateSizes(ImageDimension CanvasSize)
+{
+   if (rightImageWithoutOverlay != null)
+   {
+       int widthImage = rightImageSize.Width;
+       int heightImage = rightImageSize.Height;
+       int widthBox = CanvasSize.Width;
+       int heightBox = CanvasSize.Height;
+
+       float imageRatio = (float)widthImage / (float)heightImage;
+       float containerRatio = (float)widthBox / (float)heightBox;
+       float zoomFactor = 0;
+       if (imageRatio >= containerRatio)
+       {
+           //Image is wider than it is high
+           zoomFactor = (float)widthImage / (float)widthBox;
+       }
+       else
+       {
+           //Image is higher than it is wide
+           zoomFactor = (float)heightImage / (float)heightBox;
+       }
+       markerRadius = (int)(10 * zoomFactor);
+       deletionRadius = (int)(5 * zoomFactor);
+   }
+}
+*/
 
         /// <summary>
         /// If there is unsaved progress.

+ 290 - 94
SketchAssistant/SketchAssistantWPF/MVP_Presenter.cs

@@ -8,6 +8,8 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Shapes;
+using OptiTrack;
+using System.Windows.Ink;
 
 namespace SketchAssistantWPF
 {
@@ -25,14 +27,22 @@ namespace SketchAssistantWPF
         /// A dictionary connecting the id of an InternalLine with the respective Polyline in the right canvas.
         /// </summary>
         Dictionary<int, Shape> rightPolyLines;
-
-        ImageDimension CanvasSizeLeft = new ImageDimension(0,0);
-
-        ImageDimension CanvasSizeRight = new ImageDimension(0, 0);
-
-        ImageDimension ImageSizeLeft = new ImageDimension(0, 0);
-
-        ImageDimension ImageSizeRight = new ImageDimension(0, 0);
+        /// <summary>
+        /// The actual size of the left canvas.
+        /// </summary>
+        ImageDimension canvasSizeLeft = new ImageDimension(0, 0);
+        /// <summary>
+        /// The actual size of the right canvas.
+        /// </summary>
+        ImageDimension canvasSizeRight = new ImageDimension(0, 0);
+        /// <summary>
+        /// A list of line similarities, resulting in the similarity of the whole image.
+        /// </summary>
+        List<double> imageSimilarity = new List<double>();
+        /// <summary>
+        /// The lines in the left canvas.
+        /// </summary>
+        List<InternalLine> leftLines = new List<InternalLine>();
 
         /*******************/
         /*** ENUMERATORS ***/
@@ -40,7 +50,6 @@ namespace SketchAssistantWPF
 
         public enum MouseAction
         {
-            Click,
             Down,
             Up,
             Up_Invalid,
@@ -61,7 +70,6 @@ namespace SketchAssistantWPF
         {
             programView = form;
             programModel = new MVP_Model(this);
-            //Initialize Class Variables
             fileImporter = new FileImporter();
         }
 
@@ -76,57 +84,18 @@ namespace SketchAssistantWPF
         /// <param name="rightPBS">The new size of the left picture box.</param>
         public void Resize(Tuple<int, int> leftPBS, Tuple<int, int> rightPBS)
         {
-            CanvasSizeLeft.ChangeDimension(leftPBS.Item1, leftPBS.Item2);
-            CanvasSizeRight.ChangeDimension(rightPBS.Item1, rightPBS.Item2);
-            //programModel.UpdateSizes(CanvasSizeRight);
-            programModel.ResizeEvent(CanvasSizeLeft, CanvasSizeRight);
-        }
-
-        /// <summary>
-        /// Display a new FileDialog to load a collection of lines.
-        /// </summary>
-        public void ExamplePictureToolStripMenuItemClick()
-        {
-            var okToContinue = true;
-            if (programModel.HasUnsavedProgress())
-            {
-                okToContinue = programView.ShowWarning("You have unsaved progress. Continue?");
-            }
-            if (okToContinue)
-            {
-                var fileNameTup = programView.openNewDialog("Interactive Sketch-Assistant Drawing|*.isad");
-                if (!fileNameTup.Item1.Equals("") && !fileNameTup.Item2.Equals(""))
-                {
-                    programView.SetToolStripLoadStatus(fileNameTup.Item2);
-                    try
-                    {
-                        Tuple<int, int, List<InternalLine>> values = fileImporter.ParseISADInputFile(fileNameTup.Item1);
-                        programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
-
-                        programModel.ResetRightImage();
-                        programModel.CanvasActivated();
-                        programModel.ChangeState(true);
-                        programView.EnableTimer();
-                        ClearRightLines();
-                    }
-                    catch (FileImporterException ex)
-                    {
-                        programView.ShowInfoMessage(ex.ToString());
-                    }
-                    catch (Exception ex)
-                    {
-                        programView.ShowInfoMessage("exception occured while trying to load interactive sketch-assistant drawing file:\n\n" + ex.ToString() + "\n\n" + ex.StackTrace);
-                    }
-                }
-            }
+            canvasSizeLeft.ChangeDimension(leftPBS.Item1, leftPBS.Item2);
+            canvasSizeRight.ChangeDimension(rightPBS.Item1, rightPBS.Item2);
+            programModel.ResizeEvent(canvasSizeRight);
         }
 
         /// <summary>
         /// Display a new FileDialog to a svg drawing.
         /// </summary>
-        public void SVGToolStripMenuItemClick()
+        /// <returns>True if loading was a success</returns>
+        public bool SVGToolStripMenuItemClick()
         {
-            var okToContinue = true;
+            var okToContinue = true; bool returnval = false;
             if (programModel.HasUnsavedProgress())
             {
                 okToContinue = programView.ShowWarning("You have unsaved progress. Continue?");
@@ -140,12 +109,15 @@ namespace SketchAssistantWPF
                     try
                     {
                         Tuple<int, int, List<InternalLine>> values = fileImporter.ParseSVGInputFile(fileNameTup.Item1, programModel.leftImageBoxWidth, programModel.leftImageBoxHeight);
+                        values.Item3.ForEach(line => line.MakePermanent(0)); //Make all lines permanent
                         programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
+                        programModel.ResizeEvent(canvasSizeRight);
                         programModel.ResetRightImage();
                         programModel.CanvasActivated();
                         programModel.ChangeState(true);
                         programView.EnableTimer();
                         ClearRightLines();
+                        returnval = true;
                     }
                     catch (FileImporterException ex)
                     {
@@ -157,6 +129,7 @@ namespace SketchAssistantWPF
                     }
                 }
             }
+            return returnval;
         }
 
         /// <summary>
@@ -168,6 +141,35 @@ namespace SketchAssistantWPF
             programModel.ChangeState(NowDrawing);
         }
 
+        /// <summary>
+        /// Gets whether the program is in drawing state or not.
+        /// </summary>
+        /// <returns></returns>
+        public bool GetDrawingState()
+        {
+            return programModel.inDrawingMode;
+        }
+
+        /// <summary>
+        /// Gets whether Optitrack is in use or not.
+        /// </summary>
+        /// <returns>Return optiTrackInUse</returns>
+        public bool GetOptitrackActive()
+        {
+            return programModel.optiTrackInUse;
+        }
+
+        /// <summary>
+        /// Pass-through function to change the OptiTrack-in-use state of the model
+        /// </summary>
+        public void ChangeOptiTrack(bool usingOptiTrack)
+        {
+            if (programModel.optitrackAvailable)
+            {
+                programModel.SetOptiTrack(usingOptiTrack);
+            }
+        }
+
         /// <summary>
         /// Pass-trough function to undo an action.
         /// </summary>
@@ -204,7 +206,7 @@ namespace SketchAssistantWPF
             }
             if (okToContinue)
             {
-                programModel.ResizeEvent(CanvasSizeLeft, CanvasSizeRight);
+                programModel.ResizeEvent(canvasSizeRight);
                 programModel.ResetRightImage();
                 programModel.CanvasActivated();
                 programModel.ChangeState(true);
@@ -217,7 +219,7 @@ namespace SketchAssistantWPF
         /// Pass-trough when the mouse is moved.
         /// </summary>
         /// <param name="mouseAction">The action which is sent by the View.</param>
-        /// <param name="e">The Mouse event arguments.</param>
+        /// <param name="position">The position of the mouse.</param>
         public void MouseEvent(MouseAction mouseAction, Point position)
         {
             switch (mouseAction)
@@ -234,27 +236,37 @@ namespace SketchAssistantWPF
         /// Pass-trough function that calls the correct Mouse event of the model, when the mouse is clicked.
         /// </summary>
         /// <param name="mouseAction">The action which is sent by the View.</param>
-        /// <param name="e">The Mouse event arguments.</param>
-        public void MouseEvent(MouseAction mouseAction)
+        /// <param name="strokes">The Strokes.</param>
+        public void MouseEvent(MouseAction mouseAction, StrokeCollection strokes)
         {
-            switch (mouseAction)
+
+            if (!programModel.optiTrackInUse)
             {
-                case MouseAction.Click:
-                    programModel.MouseDown();
-                    programModel.Tick();
-                    programModel.MouseUp(true);
-                    break;
-                case MouseAction.Down:
-                    programModel.MouseDown();
-                    break;
-                case MouseAction.Up:
-                    programModel.MouseUp(true);
-                    break;
-                case MouseAction.Up_Invalid:
-                    programModel.MouseUp(false);
-                    break;
-                default:
-                    break;
+                switch (mouseAction)
+                {
+                    case MouseAction.Down:
+                        programModel.StartNewLine();
+                        break;
+                    case MouseAction.Up:
+                        if (strokes.Count > 0)
+                        {
+                            StylusPointCollection sPoints = strokes.First().StylusPoints;
+                            List<Point> points = new List<Point>();
+                            foreach (StylusPoint p in sPoints)
+                                points.Add(new Point(p.X, p.Y));
+                            programModel.FinishCurrentLine(points);
+                        }
+                        else
+                        {
+                            programModel.FinishCurrentLine(true);
+                        }
+                        break;
+                    case MouseAction.Up_Invalid:
+                        programModel.FinishCurrentLine(false);
+                        break;
+                    default:
+                        break;
+                }
             }
         }
 
@@ -271,6 +283,18 @@ namespace SketchAssistantWPF
             return programView.GetCursorPosition();
         }
 
+        /// <summary>
+        /// Updates the currentline
+        /// </summary>
+        /// <param name="linepoints">The points of the current line.</param>
+        public void UpdateCurrentLine(List<Point> linepoints)
+        {
+            Polyline currentLine = new Polyline();
+            currentLine.Stroke = Brushes.Black;
+            currentLine.Points = new PointCollection(linepoints);
+            programView.DisplayCurrLine(currentLine);
+        }
+
         /// <summary>
         /// Clears all Lines in the right canvas.
         /// </summary>
@@ -278,6 +302,8 @@ namespace SketchAssistantWPF
         {
             programView.RemoveAllRightLines();
             rightPolyLines = new Dictionary<int, Shape>();
+            //Reset the similarity display
+            UpdateSimilarityScore(Double.NaN);
         }
 
         /// <summary>
@@ -285,7 +311,7 @@ namespace SketchAssistantWPF
         /// </summary>
         public void UpdateRightLines(List<Tuple<bool, InternalLine>> lines)
         {
-            foreach(Tuple<bool, InternalLine> tup in lines)
+            foreach (Tuple<bool, InternalLine> tup in lines)
             {
                 var status = tup.Item1;
                 var line = tup.Item2;
@@ -307,21 +333,22 @@ namespace SketchAssistantWPF
                 }
                 SetVisibility(rightPolyLines[line.GetID()], status);
             }
+            //Calculate similarity scores 
+            UpdateSimilarityScore(Double.NaN); var templist = lines.Where(tup => tup.Item1).ToList();
+            if (leftLines.Count > 0)
+            {
+                for (int i = 0; i < leftLines.Count; i++)
+                {
+                    if (templist.Count == i) break;
+                    UpdateSimilarityScore(GeometryCalculator.CalculateSimilarity(templist[i].Item2, leftLines[i]));
+                }
+            }
+            else if (templist.Count > 1)
+            {
+                UpdateSimilarityScore(GeometryCalculator.CalculateSimilarity(templist[templist.Count - 2].Item2, templist[templist.Count - 1].Item2));
+            }
         }
 
-        /*
-        /// <summary>
-        /// Updates the currentline
-        /// </summary>
-        /// <param name="linepoints">The points of the current line.</param>
-        public void UpdateCurrentLine(List<Point> linepoints)
-        {
-            Polyline currentLine = new Polyline();
-            currentLine.Stroke = Brushes.Black;
-            currentLine.Points = new PointCollection(linepoints);
-            programView.DisplayCurrLine(currentLine);
-        }
-        */
         /// <summary>
         /// A function to update the displayed lines in the left canvas.
         /// </summary>
@@ -337,6 +364,8 @@ namespace SketchAssistantWPF
             }
             programView.SetCanvasState("LeftCanvas", true);
             programView.SetCanvasState("RightCanvas", true);
+
+            leftLines = lines;
         }
 
         /// <summary>
@@ -348,23 +377,49 @@ namespace SketchAssistantWPF
         /// <param name="canRedo">If actions in the model can be redone</param>
         /// <param name="canvasActive">If the right canvas is active</param>
         /// <param name="graphicLoaded">If an image is loaded in the model</param>
-        public void UpdateUIState(bool inDrawingMode, bool canUndo, bool canRedo, bool canvasActive, bool graphicLoaded)
+        /// <param name="optiTrackAvailable">If there is an optitrack system available</param>
+        /// <param name="optiTrackInUse">If the optitrack system is active</param>
+        public void UpdateUIState(bool inDrawingMode, bool canUndo, bool canRedo, bool canvasActive,
+                bool graphicLoaded, bool optiTrackAvailable, bool optiTrackInUse)
         {
             Dictionary<String, MainWindow.ButtonState> dict = new Dictionary<String, MainWindow.ButtonState> {
                 {"canvasButton", MainWindow.ButtonState.Enabled }, {"drawButton", MainWindow.ButtonState.Disabled}, {"deleteButton",MainWindow.ButtonState.Disabled },
-                {"undoButton", MainWindow.ButtonState.Disabled },{"redoButton",  MainWindow.ButtonState.Disabled}};
+                {"undoButton", MainWindow.ButtonState.Disabled },{"redoButton",  MainWindow.ButtonState.Disabled}, {"drawWithOptiButton", MainWindow.ButtonState.Disabled}};
 
             if (canvasActive)
             {
                 if (inDrawingMode)
                 {
-                    dict["drawButton"] = MainWindow.ButtonState.Active;
-                    dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                    if (optiTrackAvailable)
+                    {
+                        if (optiTrackInUse)
+                        {
+                            dict["drawButton"] = MainWindow.ButtonState.Enabled;
+                            dict["drawWithOptiButton"] = MainWindow.ButtonState.Active;
+                            dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                        }
+                        else
+                        {
+                            dict["drawButton"] = MainWindow.ButtonState.Active;
+                            dict["drawWithOptiButton"] = MainWindow.ButtonState.Enabled;
+                            dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                        }
+                    }
+                    else
+                    {
+                        dict["drawButton"] = MainWindow.ButtonState.Active;
+                        dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                    }
+
                 }
                 else
                 {
                     dict["drawButton"] = MainWindow.ButtonState.Enabled;
                     dict["deleteButton"] = MainWindow.ButtonState.Active;
+                    if (optiTrackAvailable)
+                    {
+                        dict["drawWithOptiButton"] = MainWindow.ButtonState.Enabled;
+                    }
                 }
                 if (canUndo) { dict["undoButton"] = MainWindow.ButtonState.Enabled; }
                 if (canRedo) { dict["redoButton"] = MainWindow.ButtonState.Enabled; }
@@ -387,6 +442,17 @@ namespace SketchAssistantWPF
             programView.ShowInfoMessage(msg);
         }
 
+        /// <summary>
+        /// Pass-through function to desplay an Warning message in the view
+        /// </summary>
+        /// <param name="msg">The message.</param>
+
+        /// <returns>True if the user confirms (Yes), negative if he doesn't (No)</returns>
+        public bool PassWarning(String msg)
+        {
+            return programView.ShowWarning(msg);
+        }
+
         /// <summary>
         /// Pass-trough function to update the display of the last action taken.
         /// </summary>
@@ -405,10 +471,140 @@ namespace SketchAssistantWPF
             return programView.IsMousePressed();
         }
 
+        public void PassOptiTrackMessage(String stringToPass)
+        {
+            programView.SetOptiTrackText(stringToPass);
+        }
+
+        /// <summary>
+        /// Update the similarity score displayed in the UI.
+        /// </summary>
+        /// <param name="score">Score will be reset if NaN is passed, 
+        /// will be ignored if the score is not between 0 and 1</param>
+        public void UpdateSimilarityScore(double score)
+        {
+            if (Double.IsNaN(score))
+            {
+                imageSimilarity.Clear();
+                programView.SetImageSimilarityText("");
+            }
+            else
+            {
+                if (score >= 0 && score <= 1) imageSimilarity.Add(score);
+                programView.SetImageSimilarityText((imageSimilarity.Sum() / imageSimilarity.Count).ToString());
+            }
+        }
+
+        /// <summary>
+        /// Change the color of an overlay item.
+        /// </summary>
+        /// <param name="name">The name of the item in the overlay dictionary</param>
+        /// <param name="color">The color of the item</param>
+        public void SetOverlayColor(String name, Brush color)
+        {
+            Shape shape = new Ellipse();
+            if(((MainWindow)programView).overlayDictionary.ContainsKey(name))
+            {
+                shape = ((MainWindow)programView).overlayDictionary[name];
+                
+                if(name.Substring(0, 7).Equals("dotLine"))
+                    shape.Stroke = color;
+                else
+                    shape.Fill = color;
+            }
+        }
+
+        /// <summary>
+        /// Change the properties of an overlay item.
+        /// </summary>
+        /// <param name="name">The name of the item in the overlay dictionary</param>
+        /// <param name="visible">If the element should be visible</param>
+        /// <param name="position">The new position of the element</param>
+        public void SetOverlayStatus(String name, bool visible, Point position)
+        {
+            Shape shape = new Ellipse(); double visibility = 1; double xDif = 0; double yDif = 0;
+            Point point = ConvertRightCanvasCoordinateToOverlay(position);
+            
+            switch (name)
+            {
+                case "optipoint":
+                    shape = ((MainWindow)programView).overlayDictionary["optipoint"];
+                    visibility = 0.75; xDif = 2.5; yDif = 2.5;
+                    break;
+                case "startpoint":
+                    shape = ((MainWindow)programView).overlayDictionary["startpoint"];
+                    xDif = ((MainWindow)programView).markerRadius; yDif = xDif;
+                    break;
+                case "endpoint":
+                    shape = ((MainWindow)programView).overlayDictionary["endpoint"];
+                    xDif = ((MainWindow)programView).markerRadius; yDif = xDif;
+                    break;
+                default:
+                    Console.WriteLine("Unknown Overlay Item. Please check in MVP_Presenter if this item exists.");
+                    return;
+            }
+            if (visible) shape.Opacity = visibility;
+            else shape.Opacity = 0.00001;
+            shape.Margin = new Thickness(point.X - xDif, point.Y - yDif, 0, 0);
+        }
+
+        /// <summary>
+        /// Change the properties of an overlay item.
+        /// </summary>
+        /// <param name="name">The name of the item in the overlay dictionary</param>
+        /// <param name="visible">If the element should be visible</param>
+        /// <param name="pos1">The new position of the element</param>
+        /// <param name="pos2">The new position of the element</param>
+        public void SetOverlayStatus(String name, bool visible, Point pos1, Point pos2)
+        {
+            Shape shape = new Ellipse();
+            switch (name.Substring(0, 7))
+            {
+                case "dotLine":
+                    if (((MainWindow)programView).overlayDictionary.ContainsKey(name))
+                    {
+                        shape = ((MainWindow)programView).overlayDictionary[name];
+                        break;
+                    }
+                    goto default;
+                default:
+                    SetOverlayStatus(name, visible, pos1);
+                    return;
+            }
+            if (visible) shape.Opacity = 1;
+            else shape.Opacity = 0.00001;
+            Point p1 = ConvertRightCanvasCoordinateToOverlay(pos1); Point p2 = ConvertRightCanvasCoordinateToOverlay(pos2);
+            ((Line)shape).X1 = p1.X; ((Line)shape).X2 = p2.X; ((Line)shape).Y1 = p1.Y; ((Line)shape).Y2 = p2.Y;
+        }
+
+        /// <summary>
+        /// Move the optitrack pointer to a new position.
+        /// </summary>
+        /// <param name="position">Position relative to the Rightcanvas</param>
+        public void MoveOptiPoint(Point position)
+        {
+            Point point = ConvertRightCanvasCoordinateToOverlay(position);
+            Shape shape = ((MainWindow)programView).overlayDictionary["optipoint"];
+            shape.Margin = new Thickness(point.X - 2.5, point.Y - 2.5, 0, 0);
+        }
+
         /*************************/
         /*** HELPING FUNCTIONS ***/
         /*************************/
 
+        /// <summary>
+        /// Convert point relative to the right canvas to a point relative to the overlay canvas.
+        /// </summary>
+        /// <param name="p">The point to convert.</param>
+        /// <returns>The respective overlay canvas.</returns>
+        private Point ConvertRightCanvasCoordinateToOverlay(Point p)
+        {
+            MainWindow main = (MainWindow)programView;
+            double xDif = (main.CanvasLeftEdge.ActualWidth + main.LeftCanvas.ActualWidth + main.CanvasSeperator.ActualWidth);
+            double yDif = (main.ButtonToolbar.ActualHeight);
+            return new Point(p.X + xDif, p.Y + yDif);
+        }
+
         /// <summary>
         /// Sets the visibility of a polyline.
         /// </summary>

+ 15 - 0
SketchAssistant/SketchAssistantWPF/MVP_View.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Ink;
 using System.Windows.Shapes;
 
 namespace SketchAssistantWPF
@@ -120,5 +121,19 @@ namespace SketchAssistantWPF
         /// </summary>
         /// <returns>The cursor Position</returns>
         Point GetCursorPosition();
+
+        /// <summary>
+        /// Sets the contents of the last action taken indicator label.
+        /// </summary>
+        /// <param name="message">The new contents</param>
+        void SetOptiTrackText(String message);
+
+        /// <summary>
+        /// Sets the contents of the status bar label containing
+        /// the similarity score of the left and right image.
+        /// </summary>
+        /// <param name="message">The message to be set, 
+        /// will be set to the default value if left empty.</param>
+        void SetImageSimilarityText(string message);
     }
 }

+ 76 - 37
SketchAssistant/SketchAssistantWPF/MainWindow.xaml

@@ -1,33 +1,25 @@
-<Window x:Class="SketchAssistantWPF.MainWindow"
-        xmlns:local="clr-namespace:SketchAssistantWPF"
-        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        mc:Ignorable="d"
-        Title="Sketch Assistant" Height="612" Width="914" SizeChanged="Window_SizeChanged">
-    <Grid>
+<Window x:Class="SketchAssistantWPF.MainWindow" 
+    xmlns:local="clr-namespace:SketchAssistantWPF" 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="Sketch Assistant" Height="612" Width="914" SizeChanged="Window_SizeChanged">
+    <Grid x:Name="RootGrid">
         <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="auto"/>
-            <ColumnDefinition Width="auto"/>
-            <ColumnDefinition Width="170*"/>
-            <ColumnDefinition Width="55*"/>
-            <ColumnDefinition Width="225*"/>
             <ColumnDefinition Width="5"/>
-            <ColumnDefinition Width="226*"/>
-            <ColumnDefinition Width="225*"/>
-            <ColumnDefinition Width="auto"/>
-            <ColumnDefinition Width="auto"/>
+            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="5"/>
+            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="5"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="auto"/>
             <RowDefinition Height="*"/>
             <RowDefinition Height="auto"/>
         </Grid.RowDefinitions>
-        <ToolBar x:Name="MenuToolbar" Grid.Column="0" Grid.ColumnSpan="5" Grid.Row="0" Background="LightGray">
-            <Menu Background="LightGray">
+        <StackPanel x:Name="MenuToolbar" Orientation="Horizontal" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Background="LightGray">
+            <Menu Background="LightGray" VerticalAlignment="Center" Padding="5,0,0,0">
                 <MenuItem x:Name="LoadMenuButton" Header="Load">
-                    <MenuItem x:Name="ISADMenuButton" Header="Load .isad Drawing" Click="ISADMenuItem_Click"/>
                     <MenuItem x:Name="SVGMenuButton" Header="Import SVG File" Click="SVGMenuItem_Click"/>
                 </MenuItem>
                 <MenuItem x:Name="EditMenuButton" Header="Edit">
@@ -42,10 +34,11 @@
                     </MenuItem>
                 </MenuItem>
             </Menu>
-        </ToolBar>
-        <!-- All Icons in the Toolbar taken from openclipart.org -->
-        <ToolBar x:Name="DrawingToolBar" Grid.Column="6" Grid.Row="0" Grid.ColumnSpan="2" Background="LightGray">
-            <Button x:Name="CanvasButton" ToolTip="Create a new Canvas" Click="CanvasButton_Click">
+        </StackPanel>
+        <StackPanel x:Name="ButtonToolbar" Orientation="Horizontal" Grid.Column="2" Grid.Row="0" Grid.ColumnSpan="3" Background="LightGray">
+            <Canvas Name="ToolbarSpacer" Width="5" Background="LightGray" />
+            <!-- All Icons in the StackPanel taken from openclipart.org -->
+            <Button x:Name="CanvasButton" ToolTip="Create a new Canvas" Click="CanvasButton_Click" BorderThickness="0">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -74,7 +67,7 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </Button>
-            <ToggleButton x:Name="DrawButton" ToolTip="Enter Drawing Mode" Click="DrawButton_Click">
+            <ToggleButton x:Name="DrawButton" ToolTip="Enter Drawing Mode" Click="DrawButton_Click" BorderThickness="0">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -89,7 +82,7 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </ToggleButton>
-            <ToggleButton x:Name="DeleteButton" ToolTip="Enter Deletion Mode" Click="DeleteButton_Click">
+            <ToggleButton x:Name="DeleteButton" ToolTip="Enter Deletion Mode" Click="DeleteButton_Click" BorderThickness="0">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -131,7 +124,7 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </ToggleButton>
-            <Button x:Name="UndoButton" ToolTip="Undo the last action" Click="UndoButton_Click">
+            <Button x:Name="UndoButton" ToolTip="Undo the last action" Click="UndoButton_Click" BorderThickness="0">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -153,7 +146,7 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </Button>
-            <Button x:Name="RedoButton" ToolTip="Redo the last undone action" Click="RedoButton_Click">
+            <Button x:Name="RedoButton" ToolTip="Redo the last undone action" Click="RedoButton_Click" BorderThickness="0">
                 <Rectangle Width="30" Height="30">
                     <Rectangle.Fill>
                         <DrawingBrush>
@@ -177,20 +170,66 @@
                     </Rectangle.Fill>
                 </Rectangle>
             </Button>
-        </ToolBar>
-        <local:CustomCanvas x:Name="LeftCanvas" Background="SlateGray" Grid.Column="2" Grid.Row="1" Height="auto" Grid.ColumnSpan="3"/>
-        <Canvas Name="CanvasSeperator" Grid.Column="5" Grid.Row="1" Background="LightGray" />
-        <InkCanvas x:Name="RightCanvas" Background="SlateGray" Grid.Column="6" Grid.Row="1" Height="auto"
-            PreviewMouseDown="RightCanvas_MouseDown" MouseUp="RightCanvas_MouseUp" MouseMove="RightCanvas_MouseMove" Grid.ColumnSpan="2" 
-                    StrokeCollected="RightCanvas_StrokeCollection" EditingMode="None"/>
-
+            <ToggleButton x:Name="DrawWithOptiButton" ToolTip="Enter Drawing Mode (OptiTrack)" Click="DrawWithOptiButton_Click" BorderThickness="0">
+                <Rectangle Width="30" Height="30">
+                    <Rectangle.Fill>
+                        <DrawingBrush>
+                            <DrawingBrush.Drawing>
+                                <DrawingGroup Transform="1,0,0,1,-30.972436,-202.26931">
+                                    <GeometryDrawing Brush="#FF000000">
+                                        <GeometryDrawing.Pen>
+                                            <Pen Brush="#FF000000" Thickness="1.01090431" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
+                                        </GeometryDrawing.Pen>
+                                        <GeometryDrawing.Geometry>
+                                            <RectangleGeometry RadiusX="11.1847" RadiusY="11.1847" Rect="92.1615,202.7748,95.2826,53.7929" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                    <GeometryDrawing Brush="#FF000000">
+                                        <GeometryDrawing.Pen>
+                                            <Pen Brush="#FF000000" Thickness="2.57952762" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
+                                        </GeometryDrawing.Pen>
+                                        <GeometryDrawing.Geometry>
+                                            <RectangleGeometry RadiusX="32.2622" RadiusY="32.2622" Rect="32.2622,219.6902,215.0813,155.1658" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                    <GeometryDrawing>
+                                        <GeometryDrawing.Pen>
+                                            <Pen Brush="#FFFFFFFF" Thickness="10.62992096" StartLineCap="Flat" EndLineCap="Flat" LineJoin="Miter" />
+                                        </GeometryDrawing.Pen>
+                                        <GeometryDrawing.Geometry>
+                                            <EllipseGeometry RadiusX="48.8035" RadiusY="48.8035" Center="139.8029,297.2731" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                    <GeometryDrawing Brush="#FFFFFFFF">
+                                        <GeometryDrawing.Geometry>
+                                            <EllipseGeometry RadiusX="11.2372" RadiusY="11.2372" Center="66.1719,256.4501" />
+                                        </GeometryDrawing.Geometry>
+                                    </GeometryDrawing>
+                                </DrawingGroup>
+                            </DrawingBrush.Drawing>
+                        </DrawingBrush>
+                    </Rectangle.Fill>
+                </Rectangle>
+            </ToggleButton>
+        </StackPanel>
+        <Canvas Name="CanvasLeftEdge" Grid.Column="0" Grid.Row="1" Background="LightGray" />
+        <Canvas x:Name="LeftCanvas" Background="SlateGray" Grid.Column="1" Grid.Row="1" Height="auto" Grid.ColumnSpan="1"/>
+        <Canvas Name="CanvasSeperator" Grid.Column="2" Grid.Row="1" Background="LightGray" />
+        <InkCanvas x:Name="RightCanvas" Background="SlateGray" Grid.Column="3" Grid.Row="1" Height="auto" PreviewMouseDown="RightCanvas_MouseDown" MouseUp="RightCanvas_MouseUp" MouseMove="RightCanvas_MouseMove" Grid.ColumnSpan="2" StrokeCollected="RightCanvas_StrokeCollection" EditingMode="None" IsStylusCapturedChanged="RightCanvas_IsStylusCapturedChanged"/>
+        <Canvas Name="CanvasRightEdge" Grid.Column="4" Grid.Row="1" Background="LightGray" />
 
         <DockPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="8">
-            <StatusBar DockPanel.Dock="Bottom" Name="StatusBar"  Background="LightGray">
+            <StatusBar DockPanel.Dock="Bottom" Name="StatusBar" Background="LightGray">
                 <TextBox Name="LoadStatusBox" Text="nothing loaded" Background="LightGray"/>
                 <Separator/>
                 <TextBox Name="LastActionBox" Text="none" Background="LightGray"/>
+                <Separator/>
+                <TextBox Name="OptiTrackBox" Text="none" Background="LightGray"/>
+                <Separator/>
+                <TextBox Name="LineSimilarityBox" Text="-" Background="LightGray"/>
             </StatusBar>
         </DockPanel>
+        <Ellipse x:Name="optipoint" Opacity="0.00001" Height="5" Width="5" Fill="Black"/>
+        <Canvas x:Name="OverlayCanvas" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="5" Grid.RowSpan="3"/>
     </Grid>
 </Window>

+ 165 - 48
SketchAssistant/SketchAssistantWPF/MainWindow.xaml.cs

@@ -1,4 +1,5 @@
 using Microsoft.Win32;
+using OptiTrack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -19,6 +20,7 @@ using System.Windows.Navigation;
 using System.Windows.Shapes;
 using System.Windows.Threading;
 using System.Windows.Ink;
+using System.Windows.Media.Effects;
 
 namespace SketchAssistantWPF
 {
@@ -39,7 +41,7 @@ namespace SketchAssistantWPF
                     InDebugMode = true;
                 }
             }
-            if(!InDebugMode)
+            if (!InDebugMode)
             {
                 DebugMode.Visibility = Visibility.Collapsed;
             }
@@ -47,9 +49,11 @@ namespace SketchAssistantWPF
             //  DispatcherTimer setup
             dispatcherTimer = new DispatcherTimer(DispatcherPriority.Render);
             dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
-            dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);
+            dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 5);
             ProgramPresenter.Resize(new Tuple<int, int>((int)LeftCanvas.Width, (int)LeftCanvas.Height),
                 new Tuple<int, int>((int)RightCanvas.Width, (int)RightCanvas.Height));
+            //Setup overlay items
+            SetupOverlay();
         }
 
         public enum ButtonState
@@ -91,12 +95,32 @@ namespace SketchAssistantWPF
         /// <summary>
         /// Stores Lines drawn on RightCanvas.
         /// </summary>
-        StrokeCollection strokeCollection = new StrokeCollection();
+        public StrokeCollection strokeCollection = new StrokeCollection();
+        /// <summary>
+        /// Size of areas marking endpoints of lines in the redraw mode.
+        /// </summary>
+        public int markerRadius = 5;
+        /// <summary>
+        /// Dictionary containing the overlay elements
+        /// </summary>
+        public Dictionary<String, Shape> overlayDictionary = new Dictionary<string, Shape>();
 
         /********************************************/
         /*** WINDOW SPECIFIC FUNCTIONS START HERE ***/
         /********************************************/
 
+        /// <summary>
+        /// Window closed event, connected bracelet gets disconnected.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void Window_Closed(object sender, EventArgs e)
+        {
+           // LocalArmbandInterface.deleteArmband();
+            Console.WriteLine("armband deleted 2");
+            dispatcherTimer.Stop();
+        }
+
         /// <summary>
         /// Resize Function connected to the form resize event, will refresh the form when it is resized
         /// </summary>
@@ -112,7 +136,6 @@ namespace SketchAssistantWPF
         public void RightCanvas_StrokeCollection(object sender, InkCanvasStrokeCollectedEventArgs e)
         {
             strokeCollection.Add(e.Stroke);
-            System.Diagnostics.Debug.WriteLine(strokeCollection.Count);
         }
 
         /// <summary>
@@ -120,7 +143,7 @@ namespace SketchAssistantWPF
         /// </summary>
         private void RedoButton_Click(object sender, RoutedEventArgs e)
         {
-            ProgramPresenter.Redo();
+            if (!IsMousePressed()) ProgramPresenter.Redo();
         }
 
         /// <summary>
@@ -128,7 +151,7 @@ namespace SketchAssistantWPF
         /// </summary>
         private void UndoButton_Click(object sender, RoutedEventArgs e)
         {
-            ProgramPresenter.Undo();
+            if (!IsMousePressed()) ProgramPresenter.Undo();
         }
 
         /// <summary>
@@ -149,13 +172,32 @@ namespace SketchAssistantWPF
             RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
         }
 
+        /// <summary>
+        /// Changes the state of the program to drawing with OptiTrack
+        /// </summary>
+        private void DrawWithOptiButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (ProgramPresenter.GetOptitrackActive())
+            {
+                ProgramPresenter.ChangeOptiTrack(false);
+                if (ProgramPresenter.GetDrawingState())
+                    RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
+                else
+                    RightCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke;
+            }
+            else
+            {
+                ProgramPresenter.ChangeOptiTrack(true);
+                RightCanvas.EditingMode = InkCanvasEditingMode.None;
+            }
+        }
+
         /// <summary>
         /// Hold left mouse button to start drawing.
         /// </summary>
         private void RightCanvas_MouseDown(object sender, MouseButtonEventArgs e)
         {
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down);
-            //System.Diagnostics.Debug.WriteLine("ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down);");
+            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down, strokeCollection);
         }
 
         /// <summary>
@@ -163,18 +205,41 @@ namespace SketchAssistantWPF
         /// </summary>
         private void RightCanvas_MouseUp(object sender, MouseButtonEventArgs e)
         {
-            if(strokeCollection.Count == 0)
+            if (ProgramPresenter.GetDrawingState())
             {
-                ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up_Invalid);
+                if (strokeCollection.Count == 0)
+                {
+                    ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up_Invalid, strokeCollection);
+                }
+                else
+                {
+                    ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up, strokeCollection);
+                    RightCanvas.Strokes.RemoveAt(0);
+                    strokeCollection.RemoveAt(0);
+                }
             }
-            else
+        }
+
+        /// <summary>
+        /// Is called when a stylus is lifted, which has the same effect as releasing the mouse.
+        /// Lifting the finger when using touch also toggles this, therfore this function is sufficient.
+        /// </summary>
+        private void RightCanvas_IsStylusCapturedChanged(object sender, DependencyPropertyChangedEventArgs e)
+        {
+            System.Diagnostics.Debug.WriteLine("Stylus Capture is now: {0}", RightCanvas.IsStylusCaptured);
+            if (!RightCanvas.IsStylusCaptured)
             {
-                ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);
-                RightCanvas.Strokes.RemoveAt(0);
-                strokeCollection.RemoveAt(0);
-                System.Diagnostics.Debug.WriteLine(strokeCollection.Count);
+                if (strokeCollection.Count == 0)
+                {
+                    ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up_Invalid, strokeCollection);
+                }
+                else
+                {
+                    ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up, strokeCollection);
+                    RightCanvas.Strokes.RemoveAt(0);
+                    strokeCollection.RemoveAt(0);
+                }
             }
-            //System.Diagnostics.Debug.WriteLine("ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up);");
         }
 
         /// <summary>
@@ -183,7 +248,6 @@ namespace SketchAssistantWPF
         private void RightCanvas_MouseMove(object sender, MouseEventArgs e)
         {
             ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, e.GetPosition(RightCanvas));
-            //System.Diagnostics.Debug.WriteLine("new Point(" + ((int)e.GetPosition(RightCanvas).X).ToString() + "," + ((int)e.GetPosition(RightCanvas).Y).ToString() + "), ");
         }
 
         /// <summary>
@@ -204,15 +268,6 @@ namespace SketchAssistantWPF
         private void dispatcherTimer_Tick(object sender, EventArgs e)
         {
             ProgramPresenter.Tick();
-            //System.Diagnostics.Debug.WriteLine("ProgramPresenter.Tick();");
-        }
-
-        /// <summary>
-        /// Import button for .isad drawing, will open an OpenFileDialog
-        /// </summary>
-        private void ISADMenuItem_Click(object sender, RoutedEventArgs e)
-        {
-            ProgramPresenter.ExamplePictureToolStripMenuItemClick();
         }
 
         /// <summary>
@@ -220,7 +275,12 @@ namespace SketchAssistantWPF
         /// </summary>
         private void SVGMenuItem_Click(object sender, RoutedEventArgs e)
         {
-            ProgramPresenter.SVGToolStripMenuItemClick();
+            if (ProgramPresenter.SVGToolStripMenuItemClick())
+            {
+                ProgramPresenter.NewCanvas();
+                RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
+                RightCanvas.Strokes.Clear();
+            }
         }
 
         /*************************/
@@ -242,8 +302,10 @@ namespace SketchAssistantWPF
         /// <returns>Whether or not the mouse is pressed.</returns>
         public bool IsMousePressed()
         {
-            if (!debugRunning) {
-                return (Mouse.LeftButton.Equals(MouseButtonState.Pressed) || Mouse.RightButton.Equals(MouseButtonState.Pressed)); }
+            if (!debugRunning)
+            {
+                return (Mouse.LeftButton.Equals(MouseButtonState.Pressed) || Mouse.RightButton.Equals(MouseButtonState.Pressed));
+            }
             else return true;
         }
 
@@ -316,7 +378,7 @@ namespace SketchAssistantWPF
             newPoint.Height = 3; newPoint.Width = 3;
             newPoint.Fill = Brushes.Black;
             RightCanvas.Children.Add(newPoint);
-            newPoint.Margin = new Thickness(line.point.X - 1.5, line.point.Y - 1.5, 0,0);
+            newPoint.Margin = new Thickness(line.point.X - 1.5, line.point.Y - 1.5, 0, 0);
         }
 
         /// <summary>
@@ -366,6 +428,26 @@ namespace SketchAssistantWPF
             LastActionBox.Text = message;
         }
 
+        /// <summary>
+        /// Sets the contents of the last action taken indicator label.
+        /// </summary>
+        /// <param name="message">The new contents</param>
+        public void SetOptiTrackText(string message)
+        {
+            OptiTrackBox.Text = message;
+        }
+
+        /// Sets the contents of the status bar label containing
+        /// the similarity score of the left and right image.
+        /// </summary>
+        /// <param name="message">The message to be set, 
+        /// will be set to the default value if left empty.</param>
+        public void SetImageSimilarityText(string message)
+        {
+            if (message.Count() > 0) LineSimilarityBox.Text = message;
+            else LineSimilarityBox.Text = "-";
+        }
+
         /// <summary>
         /// Changes the states of a tool strip button.
         /// </summary>
@@ -394,6 +476,10 @@ namespace SketchAssistantWPF
                 case "redoButton":
                     buttonToChange = RedoButton;
                     break;
+                case "drawWithOptiButton":
+                    buttonToChange = DrawWithOptiButton;
+                    isToggleable = true;
+                    break;
                 default:
                     Console.WriteLine("Invalid Button was given to SetToolStripButton. \nMaybe you forgot to add a case?");
                     return;
@@ -477,24 +563,12 @@ namespace SketchAssistantWPF
             switch (canvasName)
             {
                 case ("LeftCanvas"):
-                    if (active)
-                    {
-                        LeftCanvas.Background = Brushes.White;
-                    }
-                    else
-                    {
-                        LeftCanvas.Background = Brushes.SlateGray;
-                    }
+                    if (active) LeftCanvas.Background = Brushes.White;
+                    else LeftCanvas.Background = Brushes.SlateGray;
                     break;
                 case ("RightCanvas"):
-                    if (active)
-                    {
-                        RightCanvas.Background = Brushes.White;
-                    }
-                    else
-                    {
-                        RightCanvas.Background = Brushes.SlateGray;
-                    }
+                    if (active) RightCanvas.Background = Brushes.White;
+                    else RightCanvas.Background = Brushes.SlateGray;
                     break;
                 default:
                     throw new InvalidOperationException("Unknown canvas name, Check that the canvas passed is either LeftCanvas or RightCanvas");
@@ -505,7 +579,48 @@ namespace SketchAssistantWPF
         /*** HELPING FUNCTION ***/
         /************************/
 
+        /// <summary>
+        /// A function that generates the overlay elements and sets all their values.
+        /// </summary>
+        private void SetupOverlay()
+        {
+            DropShadowEffect effect = new DropShadowEffect(); effect.ShadowDepth = 0;
+            OverlayCanvas.Background = null;
+            //Startpoint of a line to be redrawn
+            Ellipse StartPointOverlay = new Ellipse();
+            StartPointOverlay.Height = markerRadius * 2; StartPointOverlay.Width = markerRadius * 2;
+            StartPointOverlay.Fill = Brushes.Green;
+            StartPointOverlay.Effect = effect;
+            overlayDictionary.Add("startpoint", StartPointOverlay);
+            //Endpoint of a line to be redrawn
+            Ellipse EndPointOverlay = new Ellipse();
+            EndPointOverlay.Height = markerRadius * 2; EndPointOverlay.Width = markerRadius * 2;
+            EndPointOverlay.Fill = Brushes.Green;
+            EndPointOverlay.Effect = effect;
+            overlayDictionary.Add("endpoint", EndPointOverlay);
+            //Pointer of the optitrack system
+            Ellipse OptitrackMarker = new Ellipse(); OptitrackMarker.Height = 5; OptitrackMarker.Width = 5;
+            OptitrackMarker.Fill = Brushes.LightGray;
+            OptitrackMarker.Effect = effect;
+            overlayDictionary.Add("optipoint", OptitrackMarker);
+            //10 Dotted Lines for debugging (if more are needed simply extend the for-loop
+            for (int x = 0; x < 10; x++)
+            {
+                Line dotLine = new Line();
+                dotLine.Stroke = Brushes.Red;
+                dotLine.StrokeDashArray = new DoubleCollection { 2 + x, 2 + x };
+                dotLine.StrokeThickness = 1;
+                overlayDictionary.Add("dotLine" + x.ToString(), dotLine);
+            }
 
+            //Common features of all overlay items
+            foreach (KeyValuePair<String, Shape> s in overlayDictionary)
+            {
+                OverlayCanvas.Children.Add(s.Value);
+                s.Value.Opacity = 0.00001;
+                s.Value.IsHitTestVisible = false;
+            }
+        }
 
         /// <summary>
         /// Sends inputs to the presenter simulating drawing, used for testing and debugging.
@@ -543,6 +658,8 @@ namespace SketchAssistantWPF
             Debug(4);
         }
 
+
+
         /// <summary>
         /// A function which simulates canvas input for debugging.
         /// </summary>
@@ -573,7 +690,7 @@ namespace SketchAssistantWPF
             debugRunning = true;
             ProgramPresenter.Tick(); await Task.Delay(10);
             ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, start);
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down); await Task.Delay(10);
+            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down, strokeCollection); await Task.Delay(10);
             for (int x = 0; x < points.Length; x++)
             {
                 ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, points[x]);
@@ -584,7 +701,7 @@ namespace SketchAssistantWPF
                     await Task.Delay(1);
                 }
             }
-            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up); await Task.Delay(1);
+            ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up, strokeCollection); await Task.Delay(1);
             debugRunning = false;
             dispatcherTimer.Start();
         }

BIN
SketchAssistant/SketchAssistantWPF/NPTrackingTools.dll


+ 195 - 0
SketchAssistant/SketchAssistantWPF/OptiTrackConnector.cs

@@ -0,0 +1,195 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace OptiTrack
+{
+
+    public class OptiTrackConnector
+    {
+        private bool _stop = true;
+        private Thread _tracker;
+
+        public delegate void OnFrameReady(Frame frame);
+
+        public bool Init(String path)
+        {
+            int a = OptiTrackNativeWrapper.TT_Initialize();
+            int b = OptiTrackNativeWrapper.TT_LoadProject(path);
+            Console.WriteLine("a:" + a + ", b:" + b);
+            if (a + b != 0)
+            {
+                // breakpoint
+            }
+            return a + b == 0;
+        }
+
+        public void StartTracking(OnFrameReady frameReadyDelegate)
+        {
+            _stop = false;
+            _tracker = new Thread(delegate ()
+            {
+                HighPerformanceTimer hpt = new HighPerformanceTimer();
+                int i = 0;
+
+                hpt.Start();
+                while (!_stop)
+                {
+                    if (i++ == 200)
+                    {
+                        //Console.WriteLine("Tracker FPS: " + 200.0 / HPT.Stop());
+                        i = 0;
+                        hpt.Start();
+                    }
+                    OptiTrackNativeWrapper.TT_Update();
+
+                    frameReadyDelegate(BuildFrame());
+
+                    Thread.Sleep(15);
+                }
+            });
+            _tracker.Start();
+        }
+
+        Frame _lastFrame;
+
+        private Frame BuildFrame()
+        {
+            Frame frame = new Frame(OptiTrackNativeWrapper.TT_FrameMarkerCount(), OptiTrackNativeWrapper.TT_TrackableCount());
+
+            for (int i = 0; i < OptiTrackNativeWrapper.TT_FrameMarkerCount(); i++)
+            {
+                frame.Markers[i] = new Marker(
+                    OptiTrackNativeWrapper.TT_FrameMarkerX(i),
+                    OptiTrackNativeWrapper.TT_FrameMarkerY(i), 
+                    OptiTrackNativeWrapper.TT_FrameMarkerZ(i))
+                {
+                    BoundToTrackable = null
+                };
+            }
+
+            for (int i = 0; i < OptiTrackNativeWrapper.TT_TrackableCount(); i++)
+            {
+                Trackable t = new Trackable
+                {
+                    IsTracked = OptiTrackNativeWrapper.TT_IsTrackableTracked(i),
+                    Id = OptiTrackNativeWrapper.TT_TrackableID(i)
+                };
+
+                if (t.IsTracked)
+                {
+                    OptiTrackNativeWrapper.TT_TrackableLocation(i, out t.X, out t.Y, out t.Z, out t.Qx, out t.Qy, out t.Qz, out t.Qw,
+                                                             out t.Pitch, out t.Yaw, out t.Roll);
+
+                    if(_lastFrame != null)
+                    {
+                        if (_lastFrame.Trackables[i].IsAlmostSameCoordinates(t))
+                            t.IsTracked = false;
+                    }
+
+                    /*for (int j = 0; j < OptiTrackNativeWrapper.TT_TrackableMarkerCount(i); j++)
+                    {
+                        float markerRadius = trackableMarker(i, j);
+
+                        frame.markers[boundTrackable(frame, markerRadius,t)].boundToTrackable = t;
+                    }*/
+
+                }
+
+                frame.Trackables[i] = t;
+            }
+            _lastFrame = frame;
+
+            return frame;
+        }
+
+        /*private float trackableMarker(int trackableId, int markerId)
+        {
+            Marker tempMarker = new Marker(0, 0, 0);
+
+            OptiTrackNativeWrapper.TT_TrackableMarker(trackableId, markerId, out tempMarker.x, out tempMarker.y, out tempMarker.z);
+
+            float markerRadius = (float)Math.Pow(((float)Math.Pow(tempMarker.x, 2.00) + (float)Math.Pow(tempMarker.y, 2.00) + (float)Math.Pow(tempMarker.z, 2.00)),0.50);
+            markerRadius += (float)0.01;
+
+            return markerRadius;
+        }
+
+        private int boundTrackable(Frame frame, float markerRadius, Trackable trackable)
+        {
+            List<float> distanceValues = new List<float>();
+
+            for (int k = 0; k < frame.markers.Count(); k++)
+                distanceValues.Add((float)Math.Pow(((float)Math.Pow(trackable.x - frame.markers[k].x, 2.00) + (float)Math.Pow(trackable.y - frame.markers[k].y, 2.00) + (float)Math.Pow(trackable.z - frame.markers[k].z, 2.00)),0.50));
+
+            float minimumDistance = markerRadius;
+            
+            int boundMarkerId = 0;
+
+            for (int i = 0; i < distanceValues.Count; ++i)
+            {
+               
+                if (distanceValues[i] <= minimumDistance)
+                {
+                    minimumDistance = distanceValues[i];
+                    boundMarkerId = i;
+                }                           
+            }
+
+            return boundMarkerId;
+        }*/
+
+        public void StopTracking()
+        {
+            _stop = true;
+            OptiTrackNativeWrapper.TT_Shutdown();
+        }
+    }
+
+    
+    class OptiTrackNativeWrapper
+    {
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_Initialize();
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_Shutdown();
+
+        [DllImport("NPTrackingTools.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_LoadProject([MarshalAs(UnmanagedType.LPStr)]string file);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_Update();
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_FrameMarkerCount();
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_TrackableCount();
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_TrackableLocation([In]int index, [Out]out float x, [Out]out float y, [Out]out float z, [Out]out float qx, [Out]out float qy, [Out]out float qz, [Out]out float qw, [Out]out float yaw, [Out]out float pitch, [Out]out float roll);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void TT_TrackableMarker([In]int rigidIndex, [In]int markerIndex, [Out]out float x, [Out]out float y, [Out]out float z);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern float TT_FrameMarkerX([In]int index);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern float TT_FrameMarkerY([In]int index);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern float TT_FrameMarkerZ([In]int index);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern bool TT_IsTrackableTracked([In]int index);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_TrackableID(int index);
+
+        [DllImport("NPTrackingTools.dll", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int TT_TrackableMarkerCount(int index);
+
+    }
+}

+ 1 - 4
SketchAssistant/SketchAssistantWPF/SketchAction.cs

@@ -72,7 +72,7 @@ namespace SketchAssistantWPF
         /// <returns>A String describing what happend at this action.</returns>
         public String GetActionInformation()
         {
-            String returnString;
+            String returnString = "";
             switch (thisAction)
             {
                 case ActionType.Start:
@@ -88,9 +88,6 @@ namespace SketchAssistantWPF
                         returnString = "Several Lines were deleted.";
                     }
                     break;
-                default:
-                    returnString = "There is no information available for this action.";
-                    break;
             }
             return returnString;
         }

+ 47 - 4
SketchAssistant/SketchAssistantWPF/SketchAssistantWPF.csproj

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props" Condition="Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" />
+  <Import Project="..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props" Condition="Exists('..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props')" />
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -44,7 +44,7 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
+    <PlatformTarget>x86</PlatformTarget>
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
     <OutputPath>bin\Release\</OutputPath>
@@ -52,6 +52,29 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>nicubunu-Quill.ico</ApplicationIcon>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
     <Reference Include="System.Data" />
@@ -68,6 +91,9 @@
     <Reference Include="WindowsBase" />
     <Reference Include="PresentationCore" />
     <Reference Include="PresentationFramework" />
+    <Reference Include="WindowsInput, Version=1.0.4.0, Culture=neutral, PublicKeyToken=9b287f7dc5073cad, processorArchitecture=MSIL">
+      <HintPath>..\packages\InputSimulator.1.0.4.0\lib\net20\WindowsInput.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <ApplicationDefinition Include="App.xaml">
@@ -76,12 +102,15 @@
     </ApplicationDefinition>
     <Compile Include="ActionHistory.cs" />
     <Compile Include="LocalArmbandInterface.cs" />
-    <Compile Include="CustomCanvas.cs" />
+    <Compile Include="Wristband.cs" />
     <Compile Include="DebugData.cs" />
     <Compile Include="FileImporter.cs" />
     <Compile Include="FileImporterException.cs" />
+    <Compile Include="Frame.cs" />
     <Compile Include="GeometryCalculator.cs" />
+    <Compile Include="HighPerformanceTimer.cs" />
     <Compile Include="ImageDimension.cs" />
+    <Compile Include="OptiTrackConnector.cs" />
     <Compile Include="SketchAction.cs" />
     <Page Include="MainWindow.xaml">
       <Generator>MSBuild:Compile</Generator>
@@ -118,6 +147,9 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
+    <None Include="optitrack_setup.ttp">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
@@ -165,11 +197,22 @@
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
+  <ItemGroup>
+    <Content Include="NPTrackingTools.dll">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <WCFMetadata Include="Connected Services\" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="nicubunu-Quill.ico" />
+  </ItemGroup>
   <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\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props'))" />
+    <Error Condition="!Exists('..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props'))" />
   </Target>
 </Project>

BIN
SketchAssistant/SketchAssistantWPF/StaticLibMotors.dll


+ 24 - 0
SketchAssistant/SketchAssistantWPF/Wristband.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace SketchAssistantWPF
+{
+    public class Wristband
+    {
+        
+        /// <summary>
+        /// Function to call when the wristband should push forwards.
+        /// </summary>
+        public void PushForward()
+        {
+            Console.WriteLine("FORWARD_PUSH");
+        }
+
+        /// <summary>
+        /// Function to call when the wristband should push backwards.
+        /// </summary>
+        public void PushBackward()
+        {
+            Console.WriteLine("BACKWARD_PUSH");
+        }
+    }
+}

BIN
SketchAssistant/SketchAssistantWPF/nicubunu-Quill.ico


BIN
SketchAssistant/SketchAssistantWPF/optitrack_setup.ttp


+ 3 - 2
SketchAssistant/SketchAssistantWPF/packages.config

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Microsoft.TestPlatform" version="16.0.0" targetFramework="net461" />
+  <package id="InputSimulator" version="1.0.4.0" targetFramework="net461" />
+  <package id="Microsoft.TestPlatform" version="16.0.1" targetFramework="net461" />
   <package id="OpenCover" version="4.7.922" targetFramework="net461" />
-  <package id="ReportGenerator" version="4.0.14" targetFramework="net461" />
+  <package id="ReportGenerator" version="4.0.15" targetFramework="net461" />
 </packages>

+ 187 - 37
SketchAssistant/StaticLib1/ArmbandInterface.cpp

@@ -2,65 +2,215 @@
 extern "C" {
 #include "MotorHeader/BodyActuator.h"
 }
-#include <ctime>
+#include <time.h>
 #include <stdio.h>
 #include "ArmbandInterface.h"
 #include <stdlib.h>
+/*
+This is a static class acting as a interface for the BodActuator dll, which is plain C code and can't be called directly from C# (managed) code.
+Therefore this class will encapsulate all memory management necessary to instantiate and use an Object of the BodyActuator type, aswell as provide all modyfying methods to external callers.
+Basically, this class acts as a single static BodyActuator instance towards outside callers, and can be used from within managed (specifically C#) code.
+(side note: the use of the terms 'function' and 'method' might be a bit messy here...)
+*/
 
+/*
+function handles are defined as a custom type to be able to dynamically bind and use methods from the C dll (no lib, neither source code for BodyActuator available, therefore only dynamic linking is possible)
+*/
+//type of the BodyActuator_init function
 typedef void(__cdecl *InitFunctionType)(BodyActuator*, BodyActuator_Type, char*, int);
-typedef void(__cdecl *StopFunctionType)(BodyActuator*, uint8_t);
+//type of the BodyActuator_clear function
+typedef void(__cdecl *ClearFunctionType)(BodyActuator*);
+//type of the BodyActuator_delete function
+typedef void(__cdecl *DeleteFunctionType)(BodyActuator*);
+//type of the BodyActuator_actuate function
+typedef void(__cdecl *ActuateFunctionType)(BodyActuator*, uint8_t, float, uint64_t);
+//type of the BodyActuator_startActuation function
 typedef void(__cdecl *StartFunctionType)(BodyActuator*, uint8_t, float);
+//type of the BodyActuator_stopActuation function
+typedef void(__cdecl *StopFunctionType)(BodyActuator*, uint8_t);
+//type of the BodyActuator_setFrequency function
+typedef void(__cdecl *SetFrequencyFunctionType)(BodyActuator*, uint8_t, uint16_t);
+//type of the BodyActuator_setIntensityRange function
+typedef void(__cdecl *SetIntensityRangeFunctionType)(BodyActuator*, uint8_t, float, float);
 
+/*
+static variables to hold the dynamically linked function handles, one per linked function in the BodyActuator dll
+*/
+//binding of the BodyActuator_init function
 static InitFunctionType initFunctionHandle;
+//binding of the BodyActuator_clear function
+static ClearFunctionType clearFunctionHandle;
+//binding of the BodyActuator_delete function
+static DeleteFunctionType deleteFunctionHandle;
+//binding of the BodyActuator_actuate function
+static ActuateFunctionType actuateFunctionHandle;
+//binding of the BodyActuator_startActuation function
 static StartFunctionType startFunctionHandle;
+//binding of the BodyActuator_stopActuation function
 static StopFunctionType stopFunctionHandle;
+//binding of the BodyActuator_setFrequency function
+static SetFrequencyFunctionType setFrequencyFunctionHandle;
+//binding of the BodyActuator_setIntensityRange function
+static SetIntensityRangeFunctionType setIntensityRangeFunctionHandle;
 
-static BodyActuator* armband;
-//static char *port = 
 
+//static variable to hold the dynamically loaded 'BodyActuator.dll' dll after loading for further processing (acquiring function handles)
 static HINSTANCE lib;
 
+//static variable holding the instance of BodyActuator on/with which the methods are called
+static BodyActuator* armband;
+
+//port has to be COM5, as it is hardcoded in the given BodyActuator dll and can not be changed //static char *port = 
+//if the tactor control unit is connected to a different port: under windows 10 the Serial Port of a device can be changed via the device manager
+
+
+//specifies the export to follow C style rules (e.g. C style name mangling), only necessary for 'public' methods
 extern "C" {
+
+	/*
+	 public initialization method for external calls, loading the BodyActuator dll and linking its methods to the previously defined function 
+	 handles, aswell as creating and initializing the single static BodyActuator object afterwards (allocates memory to that pointer without 
+	 releasing it, as the pointer is still needed outside of this method -> mus be freed later)
+	 return value may be used for debugging purposes and holds no other purpose
+	 */
 	DllExport int __cdecl ArmbandInterface::setupArmband() {
-			lib = LoadLibrary(TEXT("BodyActuator.dll"));
-			if (lib == NULL) {
-				printf("ERROR: library could not be loaded");
-				return 0;
-			}
-			initFunctionHandle = (InitFunctionType)GetProcAddress(lib, "BodyActuator_init");
-			if (initFunctionHandle == NULL) {
-				printf("ERROR: init function could not be retrieved");
-				return 1;
-			}
-			startFunctionHandle = (StartFunctionType)GetProcAddress(lib, "BodyActuator_startActuation");
-			if (startFunctionHandle == NULL) {
-				printf("ERROR: start function could not be retrieved");
-				return 2;
-			}
-			stopFunctionHandle = (StopFunctionType)GetProcAddress(lib, "BodyActuator_stopActuation");
+		//load the library at runtime and assign it to the variable
+		lib = LoadLibrary(TEXT("BodyActuator.dll"));
+		if (lib == NULL) {
+			printf("ERROR: library could not be loaded");
+			return 0;
+		}
+		//bind the various function handles
+		initFunctionHandle = (InitFunctionType)GetProcAddress(lib, "BodyActuator_init");
+		if (initFunctionHandle == NULL) {
+			printf("ERROR: init function could not be retrieved");
+			return 1;
+		}
+		clearFunctionHandle = (ClearFunctionType)GetProcAddress(lib, "BodyActuator_clear");
+		if (clearFunctionHandle == NULL) {
+			printf("ERROR: init function could not be retrieved");
+			return 2;
+		}
+		deleteFunctionHandle = (DeleteFunctionType)GetProcAddress(lib, "BodyActuator_delete");
+		if (deleteFunctionHandle == NULL) {
+			printf("ERROR: delete function could not be retrieved");
+			return 3;
+		}
+		startFunctionHandle = (StartFunctionType)GetProcAddress(lib, "BodyActuator_startActuation");
+		if (startFunctionHandle == NULL) {
+			printf("ERROR: start function could not be retrieved");
+			return 4;
+		}
+		stopFunctionHandle = (StopFunctionType)GetProcAddress(lib, "BodyActuator_stopActuation");
 			if (stopFunctionHandle == NULL) {
-				printf("ERROR: stop function could not be retrieved");
-				return 3;
-			}
-			//strcpy(port, "COM5");
-			setupMotors();
-			//startVibrate(0, 1.0);
-			return -1;
+			printf("ERROR: stop function could not be retrieved");
+			return 5;
 		}
-
-		DllExport void __cdecl ArmbandInterface::startVibrate(int tactor, float intensity) {
-			(startFunctionHandle)(armband, (uint8_t)tactor, intensity);
+		actuateFunctionHandle = (ActuateFunctionType)GetProcAddress(lib, "BodyActuator_actuate");
+			if (actuateFunctionHandle == NULL) {
+			printf("ERROR: actuate function could not be retrieved");
+			return 6;
 		}
-
-		DllExport void __cdecl ArmbandInterface::stopVibrate(int tactor) {
-			(stopFunctionHandle)(armband, (uint8_t)tactor);
+		setFrequencyFunctionHandle = (SetFrequencyFunctionType)GetProcAddress(lib, "BodyActuator_setFrequency");
+		if (setFrequencyFunctionHandle == NULL) {
+			printf("ERROR: setFrequency function could not be retrieved");
+			return 7;
 		}
+		setIntensityRangeFunctionHandle = (SetIntensityRangeFunctionType)GetProcAddress(lib, "BodyActuator_setIntensityRange");
+		if (setIntensityRangeFunctionHandle == NULL) {
+			printf("ERROR: setIntensityRange function could not be retrieved");
+			return 8;
+		}
+		//initialize the BodyActuator instance
+		return setupMotors();
+	}
+
+	/*
+	resets the instance of BodyActuator to a clean state
+	*/
+	DllExport void __cdecl ArmbandInterface::clearArmband() {
+		(clearFunctionHandle)(armband);
+		//printf("armband cleared");
+	}
+
+	/*
+	destructor method destroying the instance of BodyActuator and freeing the memory held by the instance pointer
+	*/
+	DllExport void __cdecl ArmbandInterface::deleteArmband() {
+		(deleteFunctionHandle)(armband);
+		free(armband);
+		//printf("armband deleted");
+	}
+
+	/*
+	start vibrating the specified tactor (number from 0 to 7) at the specified intensity until it is stopped (explicitly or implicitly)
+	provides access to the DLLs BodyActuator_startActuation method and handles type conversion to C types required by the DLL which are not available in C#
+	*/
+	DllExport void __cdecl ArmbandInterface::startVibrate(int tactor, double intensity) {
+		(startFunctionHandle)(armband, tactor, intensity);
+		//printf("sollte gehen");
+	}
+			
+	/*
+	explicitly stop actuating the specified tactor (number from 0 to 7)
+	provides access to the DLLs BodyActuator_stopActuation method and handles type conversion to C types required by the DLL which are not available in C#
+	*/
+	DllExport void __cdecl ArmbandInterface::stopVibrate(int tactor) {
+		(stopFunctionHandle)(armband, (uint8_t)tactor);
+	}
+	
+	/*
+	make the specified tactor (number from 0 to 7) actuate at intensity 1.0 (default: between 0.0 and 1.0, but range may be set using the setIntensityRange function) for the specified duration (number of milliseconds) ,or until it is stopped
+	provides access to the DLLs BodyActuator_actuate method and handles type conversion to C types required by the DLL which are not available in C#
+	*/
+	DllExport void __cdecl ArmbandInterface::actuate100(int tactor, double intensity, int duration) {
+		(actuateFunctionHandle)(armband, tactor, 1.0, duration);
+	}
+
+	/*
+	make the specified tactor (number from 0 to 7) actuate at a intensity 0.66 (default: between 0.0 and 1.0, but range may be set using the setIntensityRange function) for the specified duration (number of milliseconds) ,or until it is stopped
+	provides access to the DLLs BodyActuator_actuate method and handles type conversion to C types required by the DLL which are not available in C#
+	*/
+	DllExport void __cdecl ArmbandInterface::actuate66(int tactor, double intensity, int duration) {
+		(actuateFunctionHandle)(armband, tactor, 0.66, duration);
 	}
 
- void ArmbandInterface::setupMotors() {
+	/*
+	make the specified tactor (number from 0 to 7) actuate at intensity 0.33 (default: between 0.0 and 1.0, but range may be set using the setIntensityRange function) for the specified duration (number of milliseconds) ,or until it is stopped
+	provides access to the DLLs BodyActuator_actuate method and handles type conversion to C types required by the DLL which are not available in C#
+	*/
+	DllExport void __cdecl ArmbandInterface::actuate33(int tactor, double intensity, int duration) {
+		(actuateFunctionHandle)(armband, tactor, 0.33, duration);
+	}
+
+	/*
+	sets the frequency of the specified tactor to a new value (unit unknown atm...)
+	*/
+	DllExport void __cdecl ArmbandInterface::setFrequency(int tactor, int frequency) {
+		(setFrequencyFunctionHandle)(armband, tactor, frequency);
+	}
+
+	/*
+	sets a new intensity range for a single actuator to make different actuators react differently even when receiving an actuation command with the same intensity (e.g. to to compensate differing tactile sensitivity on different parts of the human body)
+	*/
+	DllExport void __cdecl ArmbandInterface::setIntensityRange(int tactor, double minIntensity, double maxIntensity) {
+		(setIntensityRangeFunctionHandle)(armband, tactor, minIntensity, maxIntensity);
+	}
+}
+
+/*
+internal method to initialize the BodyActuator object (and handle the memory allocation involved)
+*/
+int ArmbandInterface::setupMotors() {
 	 char* port = (char*) "COM5";//malloc(7);
 	 armband = (BodyActuator*) malloc(sizeof(BodyActuator*));
 	 //strcpy_s(port, "COM5");
-			(initFunctionHandle) (armband, BODYACTUATOR_TYPE_EAI, port, 8);
-			printf("armband initialized");
-		}
+	 try {
+		 (initFunctionHandle)(armband, BODYACTUATOR_TYPE_EAI, port, 8);
+		 return -1;
+	 }
+	 catch (...) {
+		 return -99;
+	 }
+	//printf("armband initialized");
+}

+ 62 - 13
SketchAssistant/StaticLib1/ArmbandInterface.h

@@ -3,29 +3,78 @@
 extern "C" {
 #include "MotorHeader/BodyActuator.h"
 }
-#include <ctime>
+#include <time.h>
 #include <stdio.h>
 #define DllExport extern "C" __declspec( dllexport )
-
-
+/*
+This is the header file of a static class acting as a interface for the BodActuator dll, which is plain C code and can't be called directly from C# (managed) code.
+Therefore this class will encapsulate all memory management necessary to instantiate and use an Object of the BodyActuator type, aswell as provide all modyfying methods to external callers.
+Basically, this class acts as a single static BodyActuator instance towards outside callers, and can be used from within managed (specifically C#) code.
+(side note: the use of the terms 'function' and 'method' might be a bit messy here...)
+*/
 
 DllExport int setupArmband();
 DllExport void startVibrate(int tactor, float intensity);
 DllExport void stopVibrate(int tactor);
+DllExport void actuate100(int tactor, double intensity, int duration);
+DllExport void actuate66(int tactor, double intensity, int duration);
+DllExport void actuate33(int tactor, double intensity, int duration);
+DllExport void deleteArmband();
 
 class ArmbandInterface
 {
-	/*
-	typedef void(__cdecl *InitFunctionType)(BodyActuator*, BodyActuator_Type, char*, int);
-	static InitFunctionType initFunctionHandle;
-	typedef void(__cdecl *StartFunctionType)(BodyActuator*, uint8_t, float);
-	static StartFunctionType startFunctionHandle;
-	typedef void(__cdecl *StopFunctionType)(BodyActuator*, uint8_t);
-	static StopFunctionType stopFunctionHandle; */
-
 	public:
+		/*
+		 public initialization method for external calls, loading the BodyActuator dll and linking its methods to the previously defined function
+		 handles, aswell as creating and initializing the single static BodyActuator object afterwards (allocates memory to that pointer without
+		 releasing it, as the pointer is still needed outside of this method -> mus be freed later)
+		 return value may be used for debugging purposes and holds no other purpose
+		 */
 		__declspec(dllexport) int __cdecl  setupArmband();
-		__declspec(dllexport) void __cdecl startVibrate(int tactor, float intensity);
+		/*
+		resets the instance of BodyActuator to a clean state
+		*/
+		__declspec(dllexport) void __cdecl clearArmband();
+		/*
+		destructor method destroying the instance of BodyActuator and freeing the memory held by the instance pointer
+		*/
+		__declspec(dllexport) void __cdecl deleteArmband();
+		/*
+		start vibrating the specified tactor (number from 0 to 7) at the specified intensity until it is stopped (explicitly or implicitly)
+		provides access to the DLLs BodyActuator_startActuation method and handles type conversion to C types required by the DLL which are not available in C#
+		*/
+		__declspec(dllexport) void __cdecl startVibrate(int tactor, double intensity);
+		/*
+		explicitly stop actuating the specified tactor (number from 0 to 7)
+		provides access to the DLLs BodyActuator_stopActuation method and handles type conversion to C types required by the DLL which are not available in C#
+		*/
 		__declspec(dllexport) void __cdecl stopVibrate(int tactor);
-		void setupMotors();
+		/*
+		make the specified tactor (number from 0 to 7) actuate at intensity 1.0 (default: between 0.0 and 1.0, but range may be set using the setIntensityRange function) for the specified duration (number of milliseconds) ,or until it is stopped
+		provides access to the DLLs BodyActuator_actuate method and handles type conversion to C types required by the DLL which are not available in C#
+		*/
+		__declspec(dllexport) void __cdecl actuate100(int tactor, double intensity, int duration);
+		/*
+		make the specified tactor (number from 0 to 7) actuate at intensity 0.66 (default: between 0.0 and 1.0, but range may be set using the setIntensityRange function) for the specified duration (number of milliseconds) ,or until it is stopped
+		provides access to the DLLs BodyActuator_actuate method and handles type conversion to C types required by the DLL which are not available in C#
+		*/
+		__declspec(dllexport) void __cdecl actuate66(int tactor, double intensity, int duration);
+		/*
+		make the specified tactor (number from 0 to 7) actuate at intensity 0.33 (default: between 0.0 and 1.0, but range may be set using the setIntensityRange function) for the specified duration (number of milliseconds) ,or until it is stopped
+		provides access to the DLLs BodyActuator_actuate method and handles type conversion to C types required by the DLL which are not available in C#
+		*/
+		__declspec(dllexport) void __cdecl actuate33(int tactor, double intensity, int duration);
+		/*
+		sets the frequency of the specified tactor to a new value (unit unknown, possibly Hz...)
+		*/
+		__declspec(dllexport) void __cdecl setFrequency(int tactor, int frequency);
+		/*
+		sets a new intensity range for a single actuator to make different actuators react differently even when receiving an actuation command with the same intensity (e.g. to to compensate differing tactile sensitivity on different parts of the human body)
+		*/
+		__declspec(dllexport) void __cdecl setIntensityRange(int tactor, double minIntensity, double maxIntensity);
+	//private:
+		/*
+		internal method to initialize the BodyActuator object (and handle the memory allocation involved)
+		*/
+		int setupMotors();
 };

+ 494 - 140
SketchAssistant/WhiteTests/UITest.cs

@@ -16,6 +16,10 @@ using System.Collections.Generic;
 using System.Text.RegularExpressions;
 using WindowsInput;
 using WindowsInput.Native;
+using System.Threading.Tasks;
+using System.Linq;
+using Application = TestStack.White.Application;
+using Window = TestStack.White.UIItems.WindowItems.Window;
 
 namespace WhiteTests
 {
@@ -83,41 +87,210 @@ namespace WhiteTests
 
         public Window setupapp()
         {
+            string[] files;
+            Regex rx = new Regex(@"^(.*\\SketchAssistant\\)");
+            Match match = rx.Match(TestContext.DeploymentDirectory);
+            String SketchAssistDir = match.Groups[1].Value;
+            try
+            {
+                files = Directory.GetFiles(SketchAssistDir + @"SketchAssistantWPF\bin\", "SketchAssistantWPF.exe", SearchOption.AllDirectories);
+            }
+            catch
+            {
+                Regex rx_0 = new Regex(@"^(.*\\projects\\)");
+                Match match_0 = rx_0.Match(TestContext.DeploymentDirectory);
+                String ProjectsDir = match_0.Groups[1].Value;
+                files = Directory.GetFiles(ProjectsDir, "SketchAssistantWPF.exe", SearchOption.AllDirectories);
+            }
+
+            ProcessStartInfo processStart = new ProcessStartInfo(files[0], "-debug");
+            /*
             string outputDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
             string editedDir = outputDir.Replace("WhiteTests", "SketchAssistantWPF");
             string app_path = editedDir + @"\SketchAssistantWPF.exe";
-            ProcessStartInfo processStart = new ProcessStartInfo(app_path, "-debug");
+            ProcessStartInfo processStart = new ProcessStartInfo(app_path, "-debug");*/
             application = Application.Launch(processStart);
             return application.GetWindow("Sketch Assistant");
         }
 
+        [DataTestMethod]
+        [TestCategory("Local")]
+        [DataRow("line")]
+        [DataRow("all")]
+        [DataRow("circle_ellipse")]
+        [DataRow("path")]
+        [DataRow("polygon")]
+        [DataRow("polyline")]
+        [DataRow("rect")]
+        public void LoadSVGFileTest(String filename)
+        {
+            Window mainWindow = setupapp();
+            InputSimulator inputSimulator = new InputSimulator();
+            Thread.Sleep(30);
+            string[] files = Directory.GetFiles(getSketchAssistantDirectory() + @"\whitelisted", "*.svg", SearchOption.AllDirectories);
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("LoadMenuButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("SVGMenuButton")).Click();
+            Thread.Sleep(1000);
+            inputSimulator.Keyboard.TextEntry(getSketchAssistantDirectory() + @"whitelisted\" + filename + ".svg");
+            Thread.Sleep(1000);
+            inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
+            Thread.Sleep(1000);
+            //uncomment when running locally
+            //Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        [TestCategory("DirectInput")]
+        public void DrawLineOnCanvasTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(30);
+            InputSimulator inputSimulator = new InputSimulator();
+            MouseSimulator mouseSimulator = new MouseSimulator(inputSimulator);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            inputSimulator.Mouse.MoveMouseBy(100, 100);
+            inputSimulator.Mouse.LeftButtonDown();
+            Thread.Sleep(30);
+            inputSimulator.Mouse.MoveMouseBy(100, 100);
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonUp();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        [TestCategory("DirectInput")]
+        public void UndoLineOnCanvasTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(30);
+            InputSimulator inputSimulator = new InputSimulator();
+            MouseSimulator mouseSimulator = new MouseSimulator(inputSimulator);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            inputSimulator.Mouse.MoveMouseBy(0, 200);
+            inputSimulator.Mouse.LeftButtonDown();
+            Thread.Sleep(30);
+            inputSimulator.Mouse.MoveMouseBy(500, 300);
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonUp();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(100);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Close();
+        }
+
         [TestMethod]
-        [TestCategory("bla")]
+        [TestCategory("DirectInput")]
+        public void InvalidLineTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(30);
+            InputSimulator inputSimulator = new InputSimulator();
+            MouseSimulator mouseSimulator = new MouseSimulator(inputSimulator);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DrawButton")).Click();
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonDown();
+            inputSimulator.Mouse.MoveMouseBy(0, 200);
+            Thread.Sleep(30);
+            inputSimulator.Mouse.MoveMouseBy(500, 300);
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonUp();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            inputSimulator.Mouse.MoveMouseBy(-1000, 0);
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonDown();
+            inputSimulator.Mouse.MoveMouseBy(1000, 0);
+            inputSimulator.Mouse.LeftButtonUp();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        [TestCategory("DirectInput")]
+        public void PointsOnCanvasSimilarityTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(30);
+            InputSimulator inputSimulator = new InputSimulator();
+            MouseSimulator mouseSimulator = new MouseSimulator(inputSimulator);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Assert.AreEqual("-", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LineSimilarityBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            inputSimulator.Mouse.MoveMouseBy(0, 200);
+            inputSimulator.Mouse.LeftButtonDown();
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonUp();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Assert.AreEqual("-", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LineSimilarityBox")).Text.ToString());
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonDown();
+            Thread.Sleep(30);
+            inputSimulator.Mouse.LeftButtonUp();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Assert.AreEqual("1", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LineSimilarityBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Assert.AreEqual("-", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LineSimilarityBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Assert.AreEqual("1", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LineSimilarityBox")).Text.ToString());
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        [TestCategory("DebugInput")]
         public void CreateCanvasTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Close();
         }
 
         [TestMethod]
-        [TestCategory("bla")]
+        [TestCategory("DebugInput")]
         public void DrawLineTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
@@ -125,30 +298,31 @@ namespace WhiteTests
         }
 
         [TestMethod]
+        [TestCategory("DebugInput")]
         public void DeleteLineTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DrawButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
@@ -156,118 +330,120 @@ namespace WhiteTests
         }
 
         [TestMethod]
-        [TestCategory("bla")]
+        [TestCategory("DebugInput")]
         public void UndoTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Close();
         }
 
         [TestMethod]
-        [TestCategory("bla")]
+        [TestCategory("DebugInput")]
         public void RedoTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Close();
         }
 
         [TestMethod]
-        public void DrawSeveralLines()
+        [TestCategory("DebugInput")]
+        public void DrawSeveralLinesTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
             Thread.Sleep(30000);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Close();
         }
 
         [TestMethod]
-        public void DeleteSeveralLines()
+        [TestCategory("DebugInput")]
+        public void DeleteSeveralLinesTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
             Thread.Sleep(24000);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 1 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             Thread.Sleep(10000);
             Assert.AreEqual("Last Action: Line number 0 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
@@ -275,210 +451,286 @@ namespace WhiteTests
         }
 
         [TestMethod]
-        public void UndoSeveralLines()
+        [TestCategory("DebugInput")]
+        public void UndoSeveralLinesTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
             Thread.Sleep(30000);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Close();
         }
 
         [TestMethod]
-        public void RedoSeveralLines()
+        [TestCategory("DebugInput")]
+        public void RedoSeveralLinesTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
             Thread.Sleep(24000);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Close();
         }
 
         [TestMethod]
-        public void UndoAndRedoTests()
+        [TestCategory("DebugInput")]
+        public void UndoAndRedoTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugOne")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugTwo")).Click();
             Thread.Sleep(24000);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("RedoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
             Thread.Sleep(7000);
             Assert.AreEqual("Last Action: Line number 0 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 1 was deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        [TestCategory("DebugInput")]
+        public void UndoAndDrawTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(30);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
+            Thread.Sleep(2000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("UndoButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
+            Thread.Sleep(2000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Close();
+        }
+
+        [TestMethod]
+        [TestCategory("DebugInput")]
+        public void DeleteSeveralLinesAtOnceTest()
+        {
+            Window mainWindow = setupapp();
+            Thread.Sleep(30);
+            Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
+            Thread.Sleep(30);
+            Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
+            Thread.Sleep(2000);
+            Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
+            Thread.Sleep(2000);
+            Assert.AreEqual("Last Action: Line number 1 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
+            mainWindow.Get<Button>(SearchCriteria.ByAutomationId("DeleteButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
+            Thread.Sleep(30);
+            mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
+            Thread.Sleep(2000);
+            Assert.AreEqual("Last Action: Several Lines were deleted.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
+            Thread.Sleep(30);
             mainWindow.Close();
         }
 
         [TestMethod]
-        public void PointDraw()
+        [TestCategory("DebugInput")]
+        public void PointDrawTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugFour")).Click();
             Thread.Sleep(4000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Close();
         }
 
         [TestMethod]
-        public void NewCanvasAfterDraw()
+        [TestCategory("DebugInput")]
+        public void NewCanvasAfterDrawTest()
         {
             Window mainWindow = setupapp();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("none", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("EditMenuButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugMode")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Menu>(SearchCriteria.ByAutomationId("DebugThree")).Click();
             Thread.Sleep(4000);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             // Click on No button in warning
             Window messageBox0 = mainWindow.MessageBox("Warning");
             messageBox0.Get<Button>(SearchCriteria.ByText("No")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             // close warning
             Window messageBox1 = mainWindow.MessageBox("Warning");
             messageBox1.Close();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: Line number 0 was drawn.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Get<Button>(SearchCriteria.ByAutomationId("CanvasButton")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             // click yes button on warning
             Window messageBox2 = mainWindow.MessageBox("Warning");
             messageBox2.Get<Button>(SearchCriteria.ByText("Yes")).Click();
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             Assert.AreEqual("Last Action: A new canvas was created.", mainWindow.Get<TextBox>(SearchCriteria.ByAutomationId("LastActionBox")).Text.ToString());
-            Thread.Sleep(20);
+            Thread.Sleep(30);
             mainWindow.Close();
         }
     }
 
     [TestClass]
-    [DeploymentItem(@"WhiteTests\test_input_files\")]
     public class FileImporterTests
     {
         /// <summary>
@@ -515,7 +767,7 @@ namespace WhiteTests
             Regex rx = new Regex(@"^(.*\\SketchAssistant\\)");
             Match match = rx.Match(TestContext.DeploymentDirectory);
             String SketchAssistDir = match.Groups[1].Value;
-            if(input_file_dir == null)
+            if (input_file_dir == null)
             {
                 if (Directory.Exists(SketchAssistDir + @"\WhiteTests\test_input_files\"))
                 {
@@ -543,7 +795,7 @@ namespace WhiteTests
         /// <param name="xCoordinates">an array containing the x coordinates of the points that will be created (length divisible by 3)</param>
         /// <param name="yCoordinates">an array containing the y coordinates of the points that will be created (length divisible by 3)</param>
         [DataTestMethod]
-        [TestCategory("bla")]
+        [TestCategory("FileIO")]
         [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 })]
@@ -584,8 +836,8 @@ namespace WhiteTests
         /// </summary>
         /// <param name="file">the input file represented as an array of lines</param>
         [DataTestMethod]
-        [TestCategory("bla")]
-        [DataRow(new String[] {})]
+        [TestCategory("FileIO")]
+        [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" })]
@@ -610,12 +862,13 @@ namespace WhiteTests
                 //try to initialize the left image with an invalid isad drawing
                 Tuple<int, int, List<InternalLine>> values1 = uut.ParseISADInputForTesting(file);
             }
-            catch (FileImporterException)
+            catch (FileImporterException e)
             {
                 //save the occurence of an exception
                 correctExceptionThrown = true;
+                System.Diagnostics.Debug.WriteLine(e.ToString());
             }
-            catch (Exception e)
+            catch (Exception)
             {
                 //don't set success flag
             }
@@ -632,7 +885,7 @@ namespace WhiteTests
         {
             FileImporter uut = new FileImporter();
             string[] files = Directory.GetFiles(getSketchAssistantDirectory() + @"\whitelisted", "*.svg", SearchOption.AllDirectories);
-            
+
             Assert.IsTrue(files.Length > 0);
 
             foreach (string s in files) //parse each of the whitelisted files
@@ -643,7 +896,7 @@ namespace WhiteTests
                 {
                     uut.ParseSVGInputFile(s, 10000, 10000);
                 }
-                catch (Exception e)
+                catch (Exception)
                 {
                     noExceptionThrown = false;
                 }
@@ -671,13 +924,114 @@ namespace WhiteTests
                 }
                 catch (FileImporterException e)
                 {
+                    System.Diagnostics.Debug.WriteLine(e.ToString());
                     correctExceptionThrown = true;
                 }
-                catch (Exception e)
+                catch (Exception)
                 {
                 }
                 Assert.IsTrue(correctExceptionThrown);
             }
         }
     }
+
+    [TestClass]
+    public class SimilarityCalculationTests
+    {
+        /// <summary>
+        /// The debug data element used to generate random lines.
+        /// </summary>
+        private DebugData DebugData = new DebugData();
+
+        /// <summary>
+        /// Generates random lines and tests how similar they are. 
+        /// To test the similarity score always stays between 0 and 1.
+        /// </summary>
+        [TestMethod]
+        [TestCategory("UnitTest")]
+        public void StaysWithinParameters()
+        {
+            Parallel.For(1, 100,
+                i =>
+                {
+                    InternalLine l0 = DebugData.GetRandomLine(1, (uint)i);
+                    InternalLine l1 = DebugData.GetRandomLine(1, (uint)i);
+                    var sim = GeometryCalculator.CalculateSimilarity(l0, l1);
+                    Assert.IsTrue((sim >= 0));
+                    Assert.IsTrue((sim <= 1));
+                });
+        }
+
+        [TestMethod]
+        [TestCategory("UnitTest")]
+        public void CorrectSimilarity()
+        {
+            Parallel.ForEach(DebugData.GetSimilarityTestData(),
+                tup =>
+                {
+                    InternalLine l0 = tup.Item1;
+                    InternalLine l1 = tup.Item2;
+                    var sim = GeometryCalculator.CalculateSimilarity(l0, l1);
+                    Assert.AreEqual(tup.Item3, sim, 0.00000001);
+                });
+        }
+
+    }
+
+    [TestClass]
+    public class InternalLineUnitTests
+    {
+        /// <summary>
+        /// The debug data element used to generate random lines.
+        /// </summary>
+        private DebugData DebugData = new DebugData();
+
+        [TestMethod]
+        [TestCategory("UnitTest")]
+        public void MakePermanentTest()
+        {
+
+            List<Point> points = new List<Point>();
+            points.AddRange(DebugData.debugPoints4);
+            InternalLine uut = new InternalLine(points);
+            Assert.AreEqual(false, uut.isPoint);
+            uut.MakePermanent(5);
+            Assert.AreEqual(true, uut.isPoint);
+            Assert.AreEqual(5, uut.GetID());
+            Assert.AreEqual(0, uut.GetLength());
+        }
+
+        [DataTestMethod]
+        [TestCategory("UnitTest")]
+        [DataRow(new int[] { 1, 1, 3, 3 }, new int[] { 1, 1, 2, 2, 3, 3 }, false, 2.828427125)]
+        [DataRow(new int[] { 1, 1, 3, 3 }, new int[] { 1, 1, 2, 2, 3, 3 }, true, 2.828427125)]
+        [DataRow(new int[] { 1, 1, 1, 4, 3, 4 }, new int[] { 1, 1, 1, 2, 1, 3, 1, 4, 2, 4, 3, 4 }, false, 5)]
+        [DataRow(new int[] { 1, 1, 1, 4, 3, 4 }, new int[] { 1, 1, 1, 2, 1, 3, 1, 4, 2, 4, 3, 4 }, true, 5)]
+        public void PermanentLineTest(int[] inPoints, int[] outPoints, bool isTemp, double len)
+        {
+            List<Point> inLine = new List<Point>(); List<Point> outLine = new List<Point>();
+            for (int i = 0; i < inPoints.Length; i += 2) inLine.Add(new Point(inPoints[i], inPoints[i + 1]));
+            for (int i = 0; i < outPoints.Length; i += 2) outLine.Add(new Point(outPoints[i], outPoints[i + 1]));
+            InternalLine uut;
+            if (isTemp)
+            {
+                uut = new InternalLine(inLine);
+                var zip = inLine.Zip(uut.GetPoints(), (a, b) => new Tuple<Point, Point>(a, b));
+                foreach (Tuple<Point, Point> tup in zip)
+                {
+                    Assert.AreEqual(tup.Item1, tup.Item2);
+                }
+            }
+            else
+            {
+                uut = new InternalLine(inLine, 0);
+                var zip = outLine.Zip(uut.GetPoints(), (a, b) => new Tuple<Point, Point>(a, b));
+                foreach (Tuple<Point, Point> tup in zip)
+                {
+                    Assert.AreEqual(tup.Item1, tup.Item2);
+                }
+            }
+            Assert.AreEqual(len, uut.GetLength(), 0.000001);
+        }
+    }
 }

+ 4 - 2
SketchAssistant/WhiteTests/WhiteTests.csproj

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props" Condition="Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" />
+  <Import Project="..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props" Condition="Exists('..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props')" />
   <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>
@@ -52,9 +52,11 @@
     <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="PresentationFramework" />
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
     <Reference Include="System.Windows" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="TestStack.White, Version=0.13.0.0, Culture=neutral, PublicKeyToken=2672efbf3e161801, processorArchitecture=MSIL">
@@ -118,7 +120,7 @@
     </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'))" />
-    <Error Condition="!Exists('..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ReportGenerator.4.0.14\build\netstandard2.0\ReportGenerator.props'))" />
+    <Error Condition="!Exists('..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ReportGenerator.4.0.15\build\netstandard2.0\ReportGenerator.props'))" />
   </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')" />
   <PropertyGroup>

+ 0 - 3
SketchAssistant/WhiteTests/packages.config

@@ -2,10 +2,7 @@
 <packages>
   <package id="Castle.Core" version="4.3.1" targetFramework="net461" />
   <package id="InputSimulator" version="1.0.4.0" targetFramework="net461" />
-  <package id="Microsoft.TestPlatform" version="16.0.0" targetFramework="net461" />
   <package id="MSTest.TestAdapter" version="1.4.0" targetFramework="net461" />
   <package id="MSTest.TestFramework" version="1.4.0" targetFramework="net461" />
-  <package id="OpenCover" version="4.7.922" targetFramework="net461" />
-  <package id="ReportGenerator" version="4.0.14" targetFramework="net461" />
   <package id="TestStack.White" version="0.13.3" targetFramework="net461" />
 </packages>

+ 3 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/H_V_C_S.svg

@@ -0,0 +1,3 @@
+<svg width="1437" height="961" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+<path fill="#ffffff" stroke="#000000" stroke-width="5" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" d="M720,203.5H945V341C945,340.5 871,435.5 852,406.5 833,377.5 694,332.5 681,425.5S482,500.5 487,437.5" id="svg_2"/>
+</svg>

+ 8 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/circle_h_v_c_s.svg

@@ -0,0 +1,8 @@
+<svg width="1437" height="961" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <g>
+  <title>Layer 1</title>
+  <circle fill="#ffffff" stroke="#000000" stroke-width="5" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" cx="388" cy="217.5" r="93.60556" id="svg_1"/>
+  <path fill="#ffffff" stroke="#000000" stroke-width="5" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" d="m720,203.5h225v138c0,-0.5 -74,94.5 -93,65.5c-19,-29 -158,-74 -171,19s-199,75 -194,12" id="svg_2"/>
+ </g>
+</svg>

+ 21 - 0
SketchAssistant/WhiteTests/test_input_files/whitelisted/path2.svg

@@ -0,0 +1,21 @@
+fsfwefafaefcSC
+F
+S
+FSFewFAHBfAg
+<DSFVSF>
+<GSG FS02MAFfm="Fmseikf" />
+source: https://www.w3schools.com/
+<svg height="600" width="1200">
+  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
+  stroke-width="3" fill="none" />
+  <path d="M 100 350 q 150 -300 300 0 z M 175 200 l 150 0" stroke="blue"
+  stroke-width="5" fill="none" />
+  <path d="M 100 100 a 60 60 0 0 0 100 0 Z"
+      fill="yellow" stroke="blue" stroke-width="5" />
+  <path d="M 100 200 A 150 120 45 1 0 200 300"
+      fill="yellow" stroke="blue" stroke-width="5" />
+  <path d="M200,300 Q400,50 600,300 t400,0"
+        fill="none" stroke="red" stroke-width="5"  />
+</svg>

BIN
SketchAssistant/optitrack_setup.ttp


+ 13 - 0
userstory20.md

@@ -0,0 +1,13 @@
+# Userstory 20 
+ 
+|**ID**|20|  
+|-|-|
+|**Name**|Tracken des Fingerpoints|
+|**Beschreibung**|Als Auftraggeber wünsche ich, dass das die Position des Cursors nicht mehr durch das berühren der Videowall bestimmt wird, sondern durch einen am Finger angebrachten Trackingpoint.|
+|**Akzeptanzkriterium**|Die Position des Cursors muss mithilfe des Motion Caputre Systems bestimmt werden. Das heißt, dass der Zeichenweg durch die Bewegung der Hand bestimmt wird.|
+|Geschätzter Aufwand (Story Points)|keine|
+|Entwickler|Rumei, Dennis|
+|Umgesetzt in Iteration|keine|
+|Tatsächlicher Aufwand (Std.)|keine|
+|Velocity (Std./Story Point)|keine|
+|Bemerkungen|Keine|

Some files were not shown because too many files changed in this diff