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();
}
}
}