using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; namespace SketchAssistant { public class MVP_Model { /// /// The Presenter of the MVP-Model. /// MVP_Presenter programPresenter; /// /// History of Actions /// ActionHistory historyOfActions; /// /// The assistant responsible for the redraw mode /// RedrawAssistant redrawAss; /*******************/ /*** ENUMERATORS ***/ /*******************/ /***********************/ /*** CLASS VARIABLES ***/ /***********************/ /// /// If the program is in drawing mode. /// bool inDrawingMode; /// /// If the mouse is currently pressed or not. /// bool mousePressed; /// /// Size of deletion area /// int deletionRadius = 5; /// /// Size of areas marking endpoints of lines in the redraw mode. /// int markerRadius = 10; /// /// The Position of the Cursor in the right picture box /// Point currentCursorPosition; /// /// The Previous Cursor Position in the right picture box /// Point previousCursorPosition; /// /// Queue for the cursorPositions /// Queue cursorPositions = new Queue(); /// /// Lookup Matrix for checking postions of lines in the image /// bool[,] isFilledMatrix; /// /// Lookup Matrix for getting line ids at a certain postions of the image /// HashSet[,] linesMatrix; /// /// List of items which will be overlayed over the right canvas. /// List>> overlayItems; /// /// Width of the LeftImageBox. /// public int leftImageBoxWidth; /// /// Height of the LeftImageBox. /// public int leftImageBoxHeight; /// /// Width of the RightImageBox. /// public int rightImageBoxWidth; /// /// Height of the RightImageBox. /// public int rightImageBoxHeight; //Images Image leftImage; List leftLineList; Image rightImageWithoutOverlay; Image rightImageWithOverlay; List> rightLineList; List currentLine; public MVP_Model(MVP_Presenter presenter) { programPresenter = presenter; historyOfActions = new ActionHistory(); redrawAss = new RedrawAssistant(); rightLineList = new List>(); overlayItems = new List>>(); } /**************************/ /*** INTERNAL FUNCTIONS ***/ /**************************/ /// /// A function that returns a white canvas for a given width and height. /// /// The width of the canvas in pixels /// The height of the canvas in pixels /// The new canvas private Image GetEmptyCanvas(int width, int height) { Image image; try { image = new Bitmap(width, height); } catch (ArgumentException e) { programPresenter.PassMessageToView("The requested canvas size caused an error: \n" + e.ToString() + "\n The Canvas will be set to match your window."); image = new Bitmap(leftImageBoxWidth, leftImageBoxHeight); } Graphics graph = Graphics.FromImage(image); graph.FillRectangle(Brushes.White, 0, 0, width + 10, height + 10); return image; } /// /// Creates an empty Canvas on the left /// /// width of the new canvas in pixels /// height of the new canvas in pixels private void DrawEmptyCanvasLeft(int width, int height) { if (width == 0) { leftImage = GetEmptyCanvas(leftImageBoxWidth, leftImageBoxHeight); } else { leftImage = GetEmptyCanvas(width, height); } programPresenter.UpdateLeftImage(leftImage); } /// /// Redraws all lines in rightLineList, for which their associated boolean value equals true and calls RedrawRightOverlay. /// private void RedrawRightImage() { var workingCanvas = GetEmptyCanvas(rightImageWithoutOverlay.Width, rightImageWithoutOverlay.Height); var workingGraph = Graphics.FromImage(workingCanvas); //Lines foreach (Tuple lineBoolTuple in rightLineList) { if (lineBoolTuple.Item1) { lineBoolTuple.Item2.DrawLine(workingGraph); } } //The Line being currently drawn if (currentLine != null && currentLine.Count > 0 && inDrawingMode && mousePressed) { var currLine = new Line(currentLine); currLine.DrawLine(workingGraph); } rightImageWithoutOverlay = workingCanvas; //Redraw the Overlay if needed if (leftImage != null) { RedrawRightOverlay(); } else { programPresenter.UpdateRightImage(rightImageWithoutOverlay); } } /// /// Redraws all elements in the overlay items for which the respective boolean value is true. /// private void RedrawRightOverlay() { var workingCanvas = rightImageWithoutOverlay; var workingGraph = Graphics.FromImage(workingCanvas); foreach (Tuple> tup in overlayItems) { if (tup.Item1) { foreach (Point p in tup.Item2) { workingGraph.FillRectangle(Brushes.Green, p.X, p.Y, 1, 1); } } } rightImageWithOverlay = workingCanvas; programPresenter.UpdateRightImage(rightImageWithOverlay); } /// /// Change the status of whether or not the lines are shown. /// /// The HashSet containing the affected Line IDs. /// True if the lines should be shown, false if they should be hidden. private void ChangeLines(HashSet lines, bool shown) { var changed = false; foreach (int lineId in lines) { if (lineId <= rightLineList.Count - 1 && lineId >= 0) { rightLineList[lineId] = new Tuple(shown, rightLineList[lineId].Item2); changed = true; } } if (changed) { RedrawRightImage(); } } /// /// A function that populates the matrixes needed for deletion detection with line data. /// private void RepopulateDeletionMatrixes() { if (rightImageWithoutOverlay != null) { isFilledMatrix = new bool[rightImageWithoutOverlay.Width, rightImageWithoutOverlay.Height]; linesMatrix = new HashSet[rightImageWithoutOverlay.Width, rightImageWithoutOverlay.Height]; foreach (Tuple lineTuple in rightLineList) { if (lineTuple.Item1) { lineTuple.Item2.PopulateMatrixes(isFilledMatrix, linesMatrix); } } } } /// /// A function that checks the deletion matrixes at a certain point /// and returns all Line ids at that point and in a square around it in a certain range. /// /// The point around which to check. /// The range around the point. If range is 0, only the point is checked. /// A List of all lines. private HashSet CheckDeletionMatrixesAroundPoint(Point p, int range) { HashSet returnSet = new HashSet(); foreach (Point pnt in GeometryCalculator.FilledCircleAlgorithm(p, (int)range)) { if (pnt.X >= 0 && pnt.Y >= 0 && pnt.X < rightImageWithoutOverlay.Width && pnt.Y < rightImageWithoutOverlay.Height) { if (isFilledMatrix[pnt.X, pnt.Y]) { returnSet.UnionWith(linesMatrix[pnt.X, pnt.Y]); } } } return returnSet; } /* /// /// Will calculate the start and endpoints of the given line on the right canvas. /// /// The line. /// The size of the circle with which the endpoints of the line are marked. private Tuple, HashSet> CalculateStartAndEnd(Line line, int size) { var circle0 = GeometryCalculator.FilledCircleAlgorithm(line.GetStartPoint(), size); var circle1 = GeometryCalculator.FilledCircleAlgorithm(line.GetEndPoint(), size); var currentLineEndings = new Tuple, HashSet>(circle0, circle1); return currentLineEndings; } */ /// /// Tells the Presenter to Update the UI /// private void UpdateUI() { programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), (rightImageWithoutOverlay != null)); } /********************************************/ /*** FUNCTIONS TO INTERACT WITH PRESENTER ***/ /********************************************/ /// /// Creates an empty Canvas /// public void DrawEmptyCanvasRight() { if (leftImage == null) { rightImageWithoutOverlay = GetEmptyCanvas(leftImageBoxWidth, leftImageBoxHeight); } else { rightImageWithoutOverlay = GetEmptyCanvas(leftImage.Width, leftImage.Height); } RepopulateDeletionMatrixes(); rightImageWithOverlay = rightImageWithoutOverlay; programPresenter.UpdateRightImage(rightImageWithOverlay); } /// /// The function to set the left image. /// /// The width of the left image. /// The height of the left image. /// The List of Lines to be displayed in the left image. public void SetLeftLineList(int width, int height, List listOfLines) { var workingCanvas = GetEmptyCanvas(width,height); var workingGraph = Graphics.FromImage(workingCanvas); leftLineList = listOfLines; redrawAss = new RedrawAssistant(leftLineList); overlayItems = redrawAss.Initialize(markerRadius); //Lines foreach (Line 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>(); } /// /// Will undo the last action taken, if the action history allows it. /// public void Undo() { if (historyOfActions.CanUndo()) { HashSet affectedLines = historyOfActions.GetCurrentAction().GetLineIDs(); SketchAction.ActionType undoAction = historyOfActions.GetCurrentAction().GetActionType(); switch (undoAction) { case SketchAction.ActionType.Delete: //Deleted Lines need to be shown ChangeLines(affectedLines, true); break; case SketchAction.ActionType.Draw: //Drawn lines need to be hidden ChangeLines(affectedLines, false); break; default: break; } if(leftImage != null) { //overlayItems = redrawAss.Tick(currentCursorPosition, rightLineList, -1, false); } RedrawRightImage(); } RepopulateDeletionMatrixes(); programPresenter.PassLastActionTaken(historyOfActions.MoveAction(true)); UpdateUI(); } /// /// Will redo the last action undone, if the action history allows it. /// public void Redo() { if (historyOfActions.CanRedo()) { programPresenter.PassLastActionTaken(historyOfActions.MoveAction(false)); HashSet affectedLines = historyOfActions.GetCurrentAction().GetLineIDs(); SketchAction.ActionType redoAction = historyOfActions.GetCurrentAction().GetActionType(); switch (redoAction) { case SketchAction.ActionType.Delete: //Deleted Lines need to be redeleted ChangeLines(affectedLines, false); break; case SketchAction.ActionType.Draw: //Drawn lines need to be redrawn ChangeLines(affectedLines, true); break; default: break; } if (leftImage != null) { //overlayItems = redrawAss.Tick(currentCursorPosition, rightLineList, -1, false); } RedrawRightImage(); RepopulateDeletionMatrixes(); } UpdateUI(); } /// /// The function called by the Presenter to change the drawing state of the program. /// /// The new drawingstate of the program public void ChangeState(bool nowDrawing) { inDrawingMode = nowDrawing; UpdateUI(); } /// /// A method to get the dimensions of the right image. /// /// A tuple containing the width and height of the right image. public Tuple GetRightImageDimensions() { if (rightImageWithoutOverlay != null) { return new Tuple(rightImageWithoutOverlay.Width, rightImageWithoutOverlay.Height); } else { return new Tuple(0, 0); } } /// /// Updates the current cursor position of the model. /// /// The new cursor position public void SetCurrentCursorPosition(Point p) { currentCursorPosition = p; } /// /// Start a new Line, when the Mouse is pressed down. /// public void MouseDown() { mousePressed = true; if (inDrawingMode) { currentLine = new List(); } } /// /// Finish the current Line, when the pressed Mouse is released. /// public void MouseUp() { mousePressed = false; if (inDrawingMode && currentLine.Count > 0) { Line newLine = new Line(currentLine, rightLineList.Count); rightLineList.Add(new Tuple(true, newLine)); newLine.PopulateMatrixes(isFilledMatrix, linesMatrix); programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID()))); if(leftImage != null) { //Execute a RedrawAssistant tick with the currently finished Line //overlayItems = redrawAss.Tick(currentCursorPosition, rightLineList, newLine.GetID(), true); } RedrawRightImage(); } UpdateUI(); } /// /// Method to be called every tick. Updates the current Line, or checks for Lines to delete, depending on the drawing mode. /// public void Tick() { if (cursorPositions.Count > 0) { previousCursorPosition = cursorPositions.Dequeue(); } else { previousCursorPosition = currentCursorPosition; } cursorPositions.Enqueue(currentCursorPosition); //Drawing if (inDrawingMode && mousePressed) { var rightGraph = Graphics.FromImage(rightImageWithoutOverlay); currentLine.Add(currentCursorPosition); Line drawline = new Line(currentLine); drawline.DrawLine(rightGraph); RedrawRightOverlay(); } //Deleting if (!inDrawingMode && mousePressed) { List uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition); foreach (Point currPoint in uncheckedPoints) { HashSet linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionRadius); if (linesToDelete.Count > 0) { programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Delete, linesToDelete))); foreach (int lineID in linesToDelete) { rightLineList[lineID] = new Tuple(false, rightLineList[lineID].Item2); } RepopulateDeletionMatrixes(); if(leftImage != null) { //Redraw overlay gets ticked //overlayItems = redrawAss.Tick(currentCursorPosition, rightLineList, -1, false); } RedrawRightImage(); } } } } /// /// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas. /// public void UpdateSizes() { if (rightImageWithoutOverlay != null) { int widthImage = rightImageWithoutOverlay.Width; int heightImage = rightImageWithoutOverlay.Height; int widthBox = rightImageBoxWidth; int heightBox = rightImageBoxHeight; 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); redrawAss.SetMarkerRadius(markerRadius); deletionRadius = (int)(5 * zoomFactor); } } /// /// If there is unsaved progress. /// /// True if there is progress that has not been saved. public bool HasUnsavedProgress() { return !historyOfActions.IsEmpty(); } } }