using System;
using System.Collections.Generic;
using System.Windows;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace SketchAssistantWPF
{
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;
/***********************/
/*** CLASS VARIABLES ***/
/***********************/
///
/// If the program is in drawing mode.
///
bool inDrawingMode;
///
/// 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;
public ImageDimension leftImageSize { get; private set; }
public ImageDimension rightImageSize { get; private set; }
///
/// Indicates whether or not the canvas on the right side is active.
///
public bool canvasActive {get; set;}
///
/// Indicates if there is a graphic loaded in the left canvas.
///
public bool graphicLoaded { get; set; }
Image rightImageWithoutOverlay;
List leftLineList;
List> rightLineList;
List currentLine = new List();
public MVP_Model(MVP_Presenter presenter)
{
programPresenter = presenter;
historyOfActions = new ActionHistory();
//redrawAss = new RedrawAssistant();
//overlayItems = new List>>();
rightLineList = new List>();
canvasActive = false;
UpdateUI();
rightImageSize = new ImageDimension(0, 0);
leftImageSize = new ImageDimension(0, 0);
}
/**************************/
/*** INTERNAL FUNCTIONS ***/
/**************************/
///
/// 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)
{
foreach (int lineId in lines)
{
if (lineId <= rightLineList.Count - 1 && lineId >= 0)
{
rightLineList[lineId] = new Tuple(shown, rightLineList[lineId].Item2);
}
}
}
///
/// A function that populates the matrixes needed for deletion detection with line data.
///
private void RepopulateDeletionMatrixes()
{
if (canvasActive)
{
isFilledMatrix = new bool[rightImageSize.Width, rightImageSize.Height];
linesMatrix = new HashSet[rightImageSize.Width, rightImageSize.Height];
foreach (Tuple lineTuple in rightLineList)
{
if (lineTuple.Item1)
{
lineTuple.Item2.PopulateMatrixes(isFilledMatrix, linesMatrix);
}
}
}
}
///
/// Tells the Presenter to Update the UI
///
private void UpdateUI()
{
programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded);
}
///
/// 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 < rightImageSize.Width && pnt.Y < rightImageSize.Height)
{
if (isFilledMatrix[(int)pnt.X, (int)pnt.Y])
{
returnSet.UnionWith(linesMatrix[(int)pnt.X, (int)pnt.Y]);
}
}
}
return returnSet;
}
/********************************************/
/*** FUNCTIONS TO INTERACT WITH PRESENTER ***/
/********************************************/
///
/// A function to update the dimensions of the left and right canvas when the window is resized.
///
/// The size of the left canvas.
/// The size of the right canvas.
public void ResizeEvent(ImageDimension LeftCanvas, ImageDimension RightCanvas)
{
if(LeftCanvas.Height >= 0 && LeftCanvas.Width>= 0) { leftImageSize = LeftCanvas; }
if(RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
RepopulateDeletionMatrixes();
}
///
/// A function to reset the right image.
///
public void ResetRightImage()
{
rightLineList.Clear();
programPresenter.PassLastActionTaken(historyOfActions.Reset());
programPresenter.ClearRightLines();
}
///
/// 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)
{
leftImageSize = new ImageDimension(width, height);
rightImageSize = new ImageDimension(width, height);
leftLineList = listOfLines;
graphicLoaded = true;
programPresenter.UpdateLeftLines(leftLineList);
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>();
*/
}
///
/// A function to tell the model a new canvas was activated.
///
public void CanvasActivated()
{
canvasActive = true;
RepopulateDeletionMatrixes();
UpdateUI();
}
///
/// 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;
}
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
}
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;
}
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
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();
}
///
/// 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()
{
if (inDrawingMode)
{
currentLine.Clear();
currentLine.Add(currentCursorPosition);
}
}
///
/// Finish the current Line, when the pressed Mouse is released.
///
public void MouseUp()
{
if (inDrawingMode && currentLine.Count > 0)
{
InternalLine newLine = new InternalLine(currentLine, rightLineList.Count);
rightLineList.Add(new Tuple(true, newLine));
newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID())));
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
currentLine.Clear();
programPresenter.UpdateCurrentLine(currentLine);
}
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 && programPresenter.IsMousePressed())
{
currentLine.Add(currentCursorPosition);
programPresenter.UpdateCurrentLine(currentLine);
}
//Deleting
if (!inDrawingMode && programPresenter.IsMousePressed())
{
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();
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
}
}
}
}
///
/// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
///
/// The size of the canvas
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);
}
}
///
/// If there is unsaved progress.
///
/// True if there is progress that has not been saved.
public bool HasUnsavedProgress()
{
return !historyOfActions.IsEmpty();
}
}
}