/// Copyright (c) 2021 Iiro Iivanainen, Harri Linna, Jere Pakkanen, Riikka Vilavaara
///
/// Permission is hereby granted, free of charge, to any person obtaining
/// a copy of this software and associated documentation files (the
/// "Software"), to deal in the Software without restriction, including
/// without limitation the rights to use, copy, modify, merge, publish,
/// distribute, sublicense, and/or sell copies of the Software, and to
/// permit persons to whom the Software is furnished to do so, subject to
/// the following conditions:
///
/// The above copyright notice and this permission notice shall be included
/// in all copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
/// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
/// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
/// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
/// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
/// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///
using CoreLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace GroundhogApp
{
///
/// Interaction logic for MapControl.xaml
///
public partial class MapControl : UserControl
{
public event MouseButtonEventHandler MapMouseDown;
public event MouseButtonEventHandler DataPointMouseDown;
public double Xpos { get; set; }
public double Ypos { get; set; }
public enum Axis
{
X,
Y,
Z
}
public Axis HorizAxis { get; set; }
public Axis VerticAxis { get; set; }
///
/// Main constructor for the user control MapControl.
///
public MapControl()
{
InitializeComponent();
Scale = 100;
}
#region Source image for the slice
public static readonly DependencyProperty SliceSourceProperty
= DependencyProperty.Register("SliceSource",
typeof(BitmapImage),
typeof(MapControl),
new UIPropertyMetadata(null,
ImageChangedCallback));
///
/// What happens when SliceSource changes.
///
/// Root Element
/// New value argument
private static void ImageChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.sliceImg.Source = e.NewValue as BitmapImage;
input.UpdateAxes();
input.ResizeCanvas();
}
///
/// Source image for the slice.
///
public BitmapImage SliceSource
{
get { return (BitmapImage)GetValue(SliceSourceProperty); }
set { SetValue(SliceSourceProperty, value); }
}
#endregion
#region Showing Color-coded axes
public static readonly DependencyProperty ShowAxesProperty
= DependencyProperty.Register("ShowAxes",
typeof(bool),
typeof(MapControl),
new FrameworkPropertyMetadata(ShowAxesPropertyChangedCallback));
///
/// What happens when ShowAxes changes.
///
/// Root element
/// New value argument
public static void ShowAxesPropertyChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.UpdateAxes();
}
///
/// Wether color-coded axes are shown or not.
///
public bool ShowAxes
{
get { return (bool)GetValue(ShowAxesProperty); }
set { SetValue(ShowAxesProperty, value); }
}
#endregion
#region Horizontal Axis Color
public static readonly DependencyProperty HorizontalAxisColorProperty
= DependencyProperty.Register("HorizontalAxisColor",
typeof(Brush),
typeof(MapControl),
new UIPropertyMetadata(null, HorizontalColorChangedCallback));
///
/// What happens when HorizontalColor changes.
///
/// Root element
/// New value argument
public static void HorizontalColorChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.HorizontalAxis.Stroke = e.NewValue as Brush;
}
///
/// Color for the horizontal axis.
///
public Brush HorizontalAxisColor
{
get { return (Brush)GetValue(HorizontalAxisColorProperty); }
set { SetValue(HorizontalAxisColorProperty, value); }
}
#endregion
#region Vertical Axis Color
public static readonly DependencyProperty VerticalAxisColorProperty
= DependencyProperty.Register("VerticalAxisColor",
typeof(Brush),
typeof(MapControl),
new UIPropertyMetadata(null, VerticalColorChangedCallback));
///
/// What happens when VerticalColor changes.
///
/// Root element
/// New value argument
public static void VerticalColorChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.VerticalAxis.Stroke = e.NewValue as Brush;
}
///
/// Color for the vertical axis.
///
public Brush VerticalAxisColor
{
get { return (Brush)GetValue(VerticalAxisColorProperty); }
set { SetValue(VerticalAxisColorProperty, value); }
}
#endregion
#region Attached Data Color
public static readonly DependencyProperty AttachedDataColorProperty
= DependencyProperty.Register("AttachedDataColor",
typeof(Brush),
typeof(MapControl),
new UIPropertyMetadata(null,
AttachedDataColorChangedCallback));
///
/// What happens when AttachedDataColor changes.
///
/// Root Element
/// New value argument
public static void AttachedDataColorChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
if (input.ShowAttachedData)
{
input.RemoveAttDataPoints();
input.DrawAttDataPoints();
}
}
///
/// Color for AttachedData points.
///
public Brush AttachedDataColor
{
get { return (Brush)GetValue(AttachedDataColorProperty); }
set { SetValue(AttachedDataColorProperty, value); }
}
public static readonly DependencyProperty AttachedDataHighlightColorProperty
= DependencyProperty.Register("AttachedDataHighlightColor",
typeof(Brush),
typeof(MapControl),
new UIPropertyMetadata(null,
AttachedDataHighlightColorChangedCallback));
///
/// What happens when AttachedDataHighlightColor changes
///
///
///
public static void AttachedDataHighlightColorChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
if (input.ShowAttachedData)
{
input.RemoveAttDataPoints();
input.DrawAttDataPoints();
}
}
///
/// Color for highlighted AttachedData.
///
public Brush AttachedDataHighlightColor
{
get { return (Brush)GetValue(AttachedDataHighlightColorProperty); }
set { SetValue(AttachedDataHighlightColorProperty, value); }
}
#endregion
#region Showing Data points on map
public static readonly DependencyProperty ShowAttachedDataProperty
= DependencyProperty.Register("ShowAttachedData",
typeof(bool),
typeof(MapControl),
new FrameworkPropertyMetadata(ShowAttachedDataPropertyChangedCallback));
///
/// What happens when ShowAttachedData changes.
///
/// Root Element
/// New value argument
public static void ShowAttachedDataPropertyChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
if (input.ShowAttachedData)
{
input.DrawAttDataPoints();
return;
}
input.RemoveAttDataPoints();
}
///
/// Is Attached Data shown on Map or not.
///
public bool ShowAttachedData
{
get { return (bool)GetValue(ShowAttachedDataProperty); }
set { SetValue(ShowAttachedDataProperty, value); }
}
#endregion
#region Color for the slice's borders
public static readonly DependencyProperty SliceColorProperty
= DependencyProperty.Register("SliceColor",
typeof(Brush),
typeof(MapControl),
new UIPropertyMetadata(null, SliceColorChangedCallback));
///
/// What happens when SliceColor is changed.
///
/// Root Element
/// New value argument
public static void SliceColorChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.SliceAxis.Stroke = e.NewValue as Brush;
input.ColoredRectangle.Fill = e.NewValue as Brush;
}
///
/// Color of the axis that the slice represents.
///
public Brush SliceColor
{
get { return (Brush)GetValue(SliceColorProperty); }
set { SetValue(SliceColorProperty, value); }
}
#endregion
#region Slice Name
public static readonly DependencyProperty SliceNameProperty
= DependencyProperty.Register("SliceName",
typeof(string),
typeof(MapControl),
new UIPropertyMetadata(null, SliceNameChangedCallback));
///
/// What happens when SliceName changes.
///
/// Root element
/// New value argument
public static void SliceNameChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.TextName.Text = e.NewValue as string;
}
///
/// Name of the slice being viewed.
///
public string SliceName
{
get { return (string)GetValue(SliceNameProperty); }
set { SetValue(SliceNameProperty, value); }
}
#endregion
#region Horizontal axis location
public static readonly DependencyProperty HorizontalDepthProperty
= DependencyProperty.Register("HorizontalDepth",
typeof(int),
typeof(MapControl),
new FrameworkPropertyMetadata(HorizontalDepthChangedCallback));
///
/// What happens when HorizontalDepth changes.
///
/// Root element
/// New value argument
public static void HorizontalDepthChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.UpdateAxes();
}
///
/// Location of Horizontal axis.
///
public int HorizontalDepth
{
get { return (int)GetValue(HorizontalDepthProperty); }
set { SetValue(HorizontalDepthProperty, value); }
}
#endregion
#region Vertixal axis location
public static readonly DependencyProperty VerticalDepthProperty
= DependencyProperty.Register("VerticalDepth",
typeof(int),
typeof(MapControl),
new FrameworkPropertyMetadata(VerticalDepthChangedCallback));
///
/// What happens when VerticalDepth changes.
///
/// Root element
/// New value argument
public static void VerticalDepthChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.UpdateAxes();
}
///
/// Location of Vertical axis.
///
public int VerticalDepth
{
get { return (int)GetValue(VerticalDepthProperty); }
set { SetValue(VerticalDepthProperty, value); }
}
#endregion
#region Edit-mode on / off
public static readonly DependencyProperty CanEditConnectorsProperty
= DependencyProperty.Register("CanEditConnectors",
typeof(bool),
typeof(MapControl),
new FrameworkPropertyMetadata(CanEditConnectorsPropertyChanged));
///
/// Adding event handler for mouse clicks if option is on.
///
/// Root Element
/// New value argument
public static void CanEditConnectorsPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
if (input.CanEditConnectors)
{
input.Canv.MouseDown
+= new MouseButtonEventHandler(input.Canv_MouseDown);
return;
}
input.Canv.MouseDown
-= new MouseButtonEventHandler(input.Canv_MouseDown);
}
///
/// Can add connectors for the Map shown or not.
///
public bool CanEditConnectors
{
get { return (bool)GetValue(CanEditConnectorsProperty); }
set { SetValue(CanEditConnectorsProperty, value); }
}
#endregion
#region Map scaling
public static readonly DependencyProperty ScaleProperty
= DependencyProperty.Register("Scale",
typeof(double),
typeof(MapControl),
new FrameworkPropertyMetadata(ScalePropertyChangedCallback));
///
/// What happens when Scale changes.
///
/// Root Element
/// New value argument
public static void ScalePropertyChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
input.ResizeCanvas();
input.RemoveAttDataPoints();
input.UpdateAxes();
if (input.ShowAttachedData) input.DrawAttDataPoints();
}
///
/// Map scale
///
public double Scale
{
get { return (double)GetValue(ScaleProperty); }
set { SetValue(ScaleProperty, value); }
}
///
/// Resizes Canvas and Map according to Scale.
///
public void ResizeCanvas()
{
if (SliceSource == null) return;
SliceBorder.Width = (Scale / 100.0) * SliceSource.Width;
SliceBorder.Height = (Scale / 100.0) * SliceSource.Height;
}
#endregion
#region Attached Data list
public static readonly DependencyProperty DataListProperty
= DependencyProperty.Register("DataList",
typeof(List),
typeof(MapControl),
new UIPropertyMetadata(null, DataListChangedCallback));
///
/// What happens when DataList changes.
///
/// Root Element
/// New value argument
public static void DataListChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var input = (MapControl)d;
if (!input.ShowAttachedData) return;
input.RemoveAttDataPoints();
input.DrawAttDataPoints();
}
///
/// List of Attached data.
///
public List DataList
{
get { return (List)GetValue(DataListProperty); }
set { SetValue(DataListProperty, value); }
}
#endregion
///
/// Invoking DataPointMouseDown event.
///
/// Event sender.
/// Event arguments.
private void DataPoint_MouseDown(object sender, MouseButtonEventArgs e)
{
if (CanEditConnectors) return;
DataPointMouseDown?.Invoke(sender, e);
}
///
/// Highlighting Attached Data Point with mouseover.
///
/// Event sender.
/// Event arguments. Not used.
private void DataPoint_MouseEnter(object sender, MouseEventArgs e)
{
Rectangle rec = sender as Rectangle;
rec.Stroke = AttachedDataHighlightColor;
}
///
/// Removing highlight from Attached Data Point when mouse leaves.
///
/// Event sender.
/// Event arguments. Not used.
private void DataPoint_MouseLeave(object sender, MouseEventArgs e)
{
Rectangle rec = sender as Rectangle;
rec.Stroke = AttachedDataColor;
}
///
/// Invoking a MouseDown event on Canvas.
///
/// Event sender.
/// Event arguments.
private void Canv_MouseDown(object sender, MouseButtonEventArgs e)
{
Point p = Mouse.GetPosition(Canv);
Xpos = (int)(p.X / (Scale / 100.0)) + 1;
Ypos = (int)(p.Y / (Scale / 100.0)) + 1;
MapMouseDown?.Invoke(sender, e);
}
///
/// Event handler for text changing on ZoomBox element.
///
/// Event sender. Not used.
/// Event arguments. Not used.
private void ZoomBox_TextChanged(object sender, RoutedEventArgs e)
{
if (!this.IsInitialized) return;
if (uint.TryParse(ZoomBox.Text.Trim(), out uint value) && value > 0)
{
ZoomBox.Style = (Style)FindResource("DefaultTextBox");
ZoomBox.ToolTip = null;
ZoomSlider.Value = Math.Log(((double)value/100.0)) / Math.Log(10);
Scale = value;
}
else
{
ZoomBox.Style = (Style)FindResource("ErrorTextBox");
ZoomBox.ToolTip = "Integrals above 0";
}
}
///
/// Event handler for ZoomSlider value changing.
///
/// Event sender. Not used.
/// Event arguments. Not used.
private void ZoomSlider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs e)
{
double val = (double)Math.Pow(10,(ZoomSlider.Value));
ZoomBox.Text = ((int)(val * 100)).ToString();
}
///
/// Updating color-coded axes
///
private void UpdateAxes()
{
if (ShowAxes)
{
HorizontalAxis.Visibility = Visibility.Visible;
VerticalAxis.Visibility = Visibility.Visible;
SliceAxis.Visibility = Visibility.Visible;
}
else
{
HorizontalAxis.Visibility = Visibility.Hidden;
VerticalAxis.Visibility = Visibility.Hidden;
SliceAxis.Visibility = Visibility.Hidden;
}
HorizontalAxis.Y1 = (Scale / 100.0) * (HorizontalDepth - 1);
HorizontalAxis.Y2 = (Scale / 100.0) * (HorizontalDepth - 1);
VerticalAxis.X1 = (Scale / 100.0) * (VerticalDepth - 1);
VerticalAxis.X2 = (Scale / 100.0) * (VerticalDepth - 1);
if (SliceSource != null)
{
SliceAxis.Width = (Scale / 100.0) * SliceSource.Width;
SliceAxis.Height = (Scale / 100.0) * SliceSource.Height;
HorizontalAxis.X2 = (Scale / 100.0) * SliceSource.Width;
VerticalAxis.Y2 = (Scale / 100.0) * SliceSource.Height;
}
}
#region Draw / undraw attached data points
///
/// Removing attached data points from Map.
///
private void RemoveAttDataPoints()
{
var children = Canv.Children.OfType().ToList();
foreach (var child in children)
{
if ("AttData".Equals(child.Tag)) Canv.Children.Remove(child);
}
}
///
/// Drawing attached data points on Map from DataList.
///
private void DrawAttDataPoints()
{
if (DataList == null) return;
foreach (AttachedData att in DataList)
{
float x = 0;
float y = 0;
if (att.AttachmentPoints.Count == 0) return;
Vector3 vec = att.AttachmentPoints[0];
switch (HorizAxis)
{
case Axis.X:
y = (float)(Scale / 100.0) * vec.X;
break;
case Axis.Y:
y = (float)(Scale / 100.0) * vec.Y;
break;
case Axis.Z:
y = (float)(Scale / 100.0) * vec.Z;
break;
default:
break;
}
switch (VerticAxis)
{
case Axis.X:
x = (float)(Scale / 100.0) * vec.X;
break;
case Axis.Y:
x = (float)(Scale / 100.0) * vec.Y;
break;
case Axis.Z:
x = (float)(Scale / 100.0) * vec.Z;
break;
default:
break;
}
DrawPoint(x, y, att.DataName);
}
}
///
/// Drawing a single Attached Data Point to specified location.
///
/// Horizontal position
/// Vertical position
/// Data Point's unique ID
private void DrawPoint(float x, float y, string id)
{
Rectangle point = new();
point.StrokeThickness = 2;
point.Stroke = AttachedDataColor;
point.Width = 5;
point.Height = 5;
point.Tag = "AttData";
point.Fill = Brushes.Black;
point.MouseEnter += DataPoint_MouseEnter;
point.MouseLeave += DataPoint_MouseLeave;
point.Uid = id;
point.MouseDown += DataPoint_MouseDown;
Canv.Children.Add(point);
Panel.SetZIndex(point, 1);
Canvas.SetTop(point, x - (point.Width / 2.0));
Canvas.SetLeft(point, y - (point.Height / 2.0));
}
#endregion
}
}