using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Ink;
using System.Windows.Media.Effects;
namespace SketchAssistantWPF
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window, MVP_View
{
public MainWindow()
{
bool InDebugMode = false;
String[] commArgs = Environment.GetCommandLineArgs();
InitializeComponent();
if (commArgs.Length > 1)
{
if (commArgs[1].Equals("-debug"))
{
InDebugMode = true;
}
}
if(!InDebugMode)
{
DebugMode.Visibility = Visibility.Collapsed;
}
ProgramPresenter = new MVP_Presenter(this);
// DispatcherTimer setup
dispatcherTimer = new DispatcherTimer(DispatcherPriority.Render);
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);
ProgramPresenter.Resize(new Tuple((int)LeftCanvas.Width, (int)LeftCanvas.Height),
new Tuple((int)RightCanvas.Width, (int)RightCanvas.Height));
//Setup overlay items
SetupOverlay();
}
public enum ButtonState
{
Enabled,
Disabled,
Active
}
DispatcherTimer dispatcherTimer;
///
/// Dialog to select a file.
///
OpenFileDialog openFileDialog = new OpenFileDialog();
///
/// All Lines in the current session
///
List> rightLineList = new List>();
///
/// Queue for the cursorPositions
///
Queue cursorPositions = new Queue();
///
/// The Presenter Component of the MVP-Model
///
MVP_Presenter ProgramPresenter;
///
/// The line currently being drawn
///
Polyline currentLine;
///
/// If the debug function is running.
///
bool debugRunning = false;
///
/// Point collections for debugging.
///
DebugData debugDat = new DebugData();
///
/// Stores Lines drawn on RightCanvas.
///
public StrokeCollection strokeCollection = new StrokeCollection();
///
/// Size of areas marking endpoints of lines in the redraw mode.
///
public int markerRadius = 5;
///
/// Dictionary containing the overlay elements
///
public Dictionary OverlayDictionary = new Dictionary();
/********************************************/
/*** WINDOW SPECIFIC FUNCTIONS START HERE ***/
/********************************************/
///
/// Resize Function connected to the form resize event, will refresh the form when it is resized
///
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
ProgramPresenter.Resize(new Tuple((int)LeftCanvas.ActualWidth, (int)LeftCanvas.ActualHeight),
new Tuple((int)RightCanvas.ActualWidth, (int)RightCanvas.ActualHeight));
}
///
/// Collects all Strokes on RightCanvas
///
public void RightCanvas_StrokeCollection(object sender, InkCanvasStrokeCollectedEventArgs e)
{
strokeCollection.Add(e.Stroke);
}
///
/// Redo an Action.
///
private void RedoButton_Click(object sender, RoutedEventArgs e)
{
if (!IsMousePressed()) ProgramPresenter.Redo();
}
///
/// Undo an Action.
///
private void UndoButton_Click(object sender, RoutedEventArgs e)
{
if(!IsMousePressed()) ProgramPresenter.Undo();
}
///
/// Changes the state of the program to deletion
///
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
ProgramPresenter.ChangeState(false);
RightCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke;
}
///
/// Changes the state of the program to drawing
///
private void DrawButton_Click(object sender, RoutedEventArgs e)
{
ProgramPresenter.ChangeState(true);
RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
///
/// Hold left mouse button to start drawing.
///
private void RightCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Down, strokeCollection);
}
///
/// Lift left mouse button to stop drawing and add a new Line.
///
private void RightCanvas_MouseUp(object sender, MouseButtonEventArgs e)
{
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);
}
}
///
/// 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.
///
private void RightCanvas_IsStylusCapturedChanged(object sender, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Stylus Capture is now: {0}", RightCanvas.IsStylusCaptured);
if (!RightCanvas.IsStylusCaptured)
{
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);
}
}
}
///
/// Get current Mouse positon within the right picture box.
///
private void RightCanvas_MouseMove(object sender, MouseEventArgs e)
{
ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, e.GetPosition(RightCanvas));
}
///
/// Button to create a new Canvas. Will create an empty image
/// which is the size of the left image, if there is one.
/// If there is no image loaded the canvas will be the size of the right picture box
///
private void CanvasButton_Click(object sender, RoutedEventArgs e)
{
ProgramPresenter.NewCanvas();
RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
RightCanvas.Strokes.Clear();
}
///
/// Ticks the Presenter.
///
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
ProgramPresenter.Tick();
}
///
/// Import button for .svg file, will open an OpenFileDialog
///
private void SVGMenuItem_Click(object sender, RoutedEventArgs e)
{
if(ProgramPresenter.SVGToolStripMenuItemClick())
RightCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
/*************************/
/*** PRESENTER -> VIEW ***/
/*************************/
///
/// Returns the cursor position.
///
/// The cursor Position
public Point GetCursorPosition()
{
return Mouse.GetPosition(RightCanvas);
}
///
/// If the mouse is pressed or not.
///
/// Whether or not the mouse is pressed.
public bool IsMousePressed()
{
if (!debugRunning) {
return (Mouse.LeftButton.Equals(MouseButtonState.Pressed) || Mouse.RightButton.Equals(MouseButtonState.Pressed)); }
else return true;
}
///
/// Remove the current line.
///
public void RemoveCurrLine()
{
RightCanvas.Children.Remove(currentLine);
}
///
/// Display the current line.
///
/// The current line to display
public void DisplayCurrLine(Polyline line)
{
if (RightCanvas.Children.Contains(currentLine))
{
RemoveCurrLine();
}
RightCanvas.Children.Add(line);
currentLine = line;
}
///
/// Removes all Lines from the left canvas.
///
public void RemoveAllLeftLines()
{
LeftCanvas.Children.Clear();
}
///
/// Removes all lines in the right canvas.
///
public void RemoveAllRightLines()
{
RightCanvas.Children.Clear();
}
///
/// Adds another Line that will be displayed in the left display.
///
/// The new Polyline to be added displayed.
public void AddNewLineLeft(Polyline newLine)
{
newLine.Stroke = Brushes.Black;
newLine.StrokeThickness = 2;
LeftCanvas.Children.Add(newLine);
}
///
/// Adds another Line that will be displayed in the right display.
///
/// The new Polyline to be added displayed.
public void AddNewLineRight(Polyline newLine)
{
newLine.Stroke = Brushes.Black;
newLine.StrokeThickness = 2;
RightCanvas.Children.Add(newLine);
}
///
/// Adds a point to the right canvas
///
/// The point
public void AddNewPointRight(Ellipse newPoint, InternalLine line)
{
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);
}
///
/// Adds a point to the left canvas
///
/// The point
public void AddNewPointLeft(Ellipse newPoint)
{
newPoint.Height = 3; newPoint.Width = 3;
newPoint.Fill = Brushes.Black;
LeftCanvas.Children.Add(newPoint);
}
///
/// Enables the timer of the View, which will tick the Presenter.
///
public void EnableTimer()
{
dispatcherTimer.Start();
}
///
/// A function that opens a file dialog and returns the filename.
///
/// The filter that should be applied to the new Dialog.
/// Returns the FileName and the SafeFileName if the user correctly selects a file,
/// else returns a tuple with empty strigns
public Tuple openNewDialog(string Filter)
{
openFileDialog.Filter = Filter;
if (openFileDialog.ShowDialog() == true)
{
return new Tuple(openFileDialog.FileName, openFileDialog.SafeFileName);
}
else
{
return new Tuple("", "");
}
}
///
/// Sets the contents of the last action taken indicator label.
///
/// The new contents
public void SetLastActionTakenText(string message)
{
LastActionBox.Text = message;
}
///
/// Sets the contents of the status bar label containing
/// the similarity score of the left and right image.
///
/// The message to be set,
/// will be set to the default value if left empty.
public void SetImageSimilarityText(string message)
{
if (message.Count() > 0) LineSimilarityBox.Text = message;
else LineSimilarityBox.Text = "-";
}
///
/// Changes the states of a tool strip button.
///
/// The name of the button.
/// The new state of the button.
public void SetToolStripButtonStatus(string buttonName, MainWindow.ButtonState state)
{
ButtonBase buttonToChange;
bool isToggleable = false;
switch (buttonName)
{
case "canvasButton":
buttonToChange = CanvasButton;
break;
case "drawButton":
buttonToChange = DrawButton;
isToggleable = true;
break;
case "deleteButton":
buttonToChange = DeleteButton;
isToggleable = true;
break;
case "undoButton":
buttonToChange = UndoButton;
break;
case "redoButton":
buttonToChange = RedoButton;
break;
default:
Console.WriteLine("Invalid Button was given to SetToolStripButton. \nMaybe you forgot to add a case?");
return;
}
if (isToggleable)
{
switch (state)
{
case ButtonState.Active:
((ToggleButton)buttonToChange).IsEnabled = true;
((ToggleButton)buttonToChange).IsChecked = true;
((ToggleButton)buttonToChange).Opacity = 1;
((ToggleButton)buttonToChange).Background = Brushes.SkyBlue;
break;
case ButtonState.Disabled:
((ToggleButton)buttonToChange).IsEnabled = false;
((ToggleButton)buttonToChange).IsChecked = false;
((ToggleButton)buttonToChange).Opacity = 0.5;
((ToggleButton)buttonToChange).Background = Brushes.LightGray;
break;
case ButtonState.Enabled:
((ToggleButton)buttonToChange).IsEnabled = true;
((ToggleButton)buttonToChange).IsChecked = false;
((ToggleButton)buttonToChange).Opacity = 1;
((ToggleButton)buttonToChange).Background = Brushes.LightGray;
break;
}
}
else
{
switch (state)
{
case ButtonState.Disabled:
((Button)buttonToChange).IsEnabled = false;
((Button)buttonToChange).Opacity = 0.5;
break;
default:
((Button)buttonToChange).IsEnabled = true;
((Button)buttonToChange).Opacity = 1;
break;
}
}
}
///
/// Sets the contents of the load status indicator label.
///
/// The new contents
public void SetToolStripLoadStatus(string message)
{
LoadStatusBox.Text = message;
}
///
/// shows the given info message in a popup and asks the user to aknowledge it
///
/// the message to show
public void ShowInfoMessage(string message)
{
MessageBox.Show(message);
}
///
/// Shows a warning box with the given message (Yes/No Buttons)and returns true if the user aknowledges it.
///
/// The message of the warning.
/// True if the user confirms (Yes), negative if he doesn't (No)
public bool ShowWarning(string message)
{
MessageBoxResult result = MessageBox.Show(message, "Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning);
return (result.Equals(MessageBoxResult.Yes));
}
///
/// Updates the colour of a canvas.
///
/// The name of the canvas to be updated.
/// Whether or not the canvas is active.
public void SetCanvasState(string canvasName, bool active)
{
switch (canvasName)
{
case ("LeftCanvas"):
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;
}
break;
default:
throw new InvalidOperationException("Unknown canvas name, Check that the canvas passed is either LeftCanvas or RightCanvas");
}
}
/************************/
/*** HELPING FUNCTION ***/
/************************/
///
/// A function that generates the overlay elements and sets all their values.
///
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);
//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 s in OverlayDictionary)
{
OverlayCanvas.Children.Add(s.Value);
s.Value.Opacity = 0.00001;
s.Value.IsHitTestVisible = false;
}
//Enable optipoint initially
ProgramPresenter.SetOverlayStatus("optipoint", true, GetCursorPosition());
}
///
/// Sends inputs to the presenter simulating drawing, used for testing and debugging.
/// Takes 7000ms
///
private void DebugOne_Click(object sender, RoutedEventArgs e)
{
Debug(1);
}
///
/// Sends inputs to the presenter simulating drawing, used for testing and debugging.
/// Takes 24000ms
///
private void DebugTwo_Click(object sender, RoutedEventArgs e)
{
Debug(2);
}
///
/// Sends inputs to the presenter simulating drawing, used for testing and debugging.
/// Takes 4000ms
///
private void DebugThree_Click(object sender, RoutedEventArgs e)
{
Debug(3);
}
///
/// Sends inputs to the presenter simulating drawing, used for testing and debugging.
/// Takes
///
private void DebugFour_Click(object sender, RoutedEventArgs e)
{
Debug(4);
}
///
/// A function which simulates canvas input for debugging.
///
///
private async void Debug(int option)
{
Point[] points;
Point start = new Point(50, 50);
switch (option)
{
case 1:
points = debugDat.debugPoints1;
break;
case 2:
points = debugDat.debugPoints2;
break;
case 3:
points = debugDat.debugPoints3;
break;
case 4:
points = debugDat.debugPoints4;
start = new Point(284, 148);
break;
default:
return;
}
dispatcherTimer.Stop();
debugRunning = true;
ProgramPresenter.Tick(); await Task.Delay(10);
ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Move, start);
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]);
await Task.Delay(1);
if (x % 5 == 0)
{
ProgramPresenter.Tick();
await Task.Delay(1);
}
}
ProgramPresenter.MouseEvent(MVP_Presenter.MouseAction.Up, strokeCollection); await Task.Delay(1);
debugRunning = false;
dispatcherTimer.Start();
}
}
}