/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc.
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using MessageBox.Avalonia.Enums;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using UVtools.AvaloniaControls;
using UVtools.Core;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Managers;
using UVtools.Core.Operations;
using UVtools.WPF.Controls;
using UVtools.WPF.Controls.Calibrators;
using UVtools.WPF.Controls.Tools;
using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
using UVtools.WPF.Windows;
using Bitmap = Avalonia.Media.Imaging.Bitmap;
using Helpers = UVtools.WPF.Controls.Helpers;
using Path = System.IO.Path;
using Point = Avalonia.Point;
namespace UVtools.WPF
{
public partial class MainWindow : WindowEx
{
#region Redirects
public AppVersionChecker VersionChecker => App.VersionChecker;
public static ClipboardManager Clipboard => ClipboardManager.Instance;
#endregion
#region Controls
//public ProgressWindow ProgressWindow = new ();
public static MenuItem[] MenuTools { get; } =
{
new()
{
Tag = new OperationEditParameters(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/wrench-16x16.png"))
}
},
new()
{
Tag = new OperationRepairLayers(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/toolbox-16x16.png"))
}
},
new()
{
Tag = new OperationMove(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/move-16x16.png"))
}
},
new()
{
Tag = new OperationResize(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/crop-16x16.png"))
}
},
new()
{
Tag = new OperationFlip(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/flip-16x16.png"))
}
},
new()
{
Tag = new OperationRotate(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/sync-16x16.png"))
}
},
new()
{
Tag = new OperationSolidify(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/square-solid-16x16.png"))
}
},
new()
{
Tag = new OperationMorph(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/geometry-16x16.png"))
}
},
new()
{
Tag = new OperationRaftRelief(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/cookie-16x16.png"))
}
},
new()
{
Tag = new OperationRedrawModel(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/code-branch-16x16.png"))
}
},
/*new()
{
Tag = new OperationThreshold(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/th-16x16.png"))
}
},*/
new()
{
Tag = new OperationLayerArithmetic(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/square-root-16x16.png"))
}
},
new()
{
Tag = new OperationPixelArithmetic(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/pixel-16x16.png"))
}
},
new()
{
Tag = new OperationMask(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/mask-16x16.png"))
}
},
/*new()
{
Tag = new OperationPixelDimming(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/pixel-16x16.png"))
}
},*/
new()
{
Tag = new OperationLightBleedCompensation(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/lightbulb-solid-16x16.png"))
}
},
new()
{
Tag = new OperationInfill(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/stroopwafel-16x16.png"))
}
},
new()
{
Tag = new OperationBlur(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/blur-16x16.png"))
}
},
new()
{
Tag = new OperationPattern(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/pattern-16x16.png"))
}
},
new()
{
Tag = new OperationDynamicLayerHeight(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/dynamic-layers-16x16.png"))
}
},
new()
{
Tag = new OperationDynamicLifts(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/angle-double-up-16x16.png"))
}
},
new()
{
Tag = new OperationLayerReHeight(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/ladder-16x16.png"))
}
},
new()
{
Tag = new OperationRaiseOnPrintFinish(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/level-up-alt-16x16.png"))
}
},
new()
{
Tag = new OperationChangeResolution(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/resize-16x16.png"))
}
},
new()
{
Tag = new OperationScripting(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/code-16x16.png"))
}
},
new()
{
Tag = new OperationCalculator(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/calculator-16x16.png"))
}
},
};
public static MenuItem[] MenuCalibration { get; } =
{
new()
{
Tag = new OperationCalibrateExposureFinder(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/sun-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateElephantFoot(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/elephant-foot-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateXYZAccuracy(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/cubes-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateLiftHeight(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/long-arrow-alt-up-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateTolerance(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/dot-circle-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateGrayscale(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/chart-pie-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateStressTower(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/chess-rook-16x16.png"))
}
},
new()
{
Tag = new OperationCalibrateExternalTests(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/bookmark-16x16.png"))
}
},
};
public static MenuItem[] LayerActionsMenu { get; } =
{
new()
{
Tag = new OperationLayerImport(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/file-import-16x16.png"))
}
},
new()
{
Tag = new OperationLayerClone(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/copy-16x16.png"))
}
},
new()
{
Tag = new OperationLayerRemove(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/trash-16x16.png"))
}
},
new()
{
Tag = new OperationLayerExportImage(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/file-image-16x16.png"))
}
},
new()
{
Tag = new OperationLayerExportGif(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/file-gif-16x16.png"))
}
},
new()
{
Tag = new OperationLayerExportSkeleton(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/file-image-16x16.png"))
}
},
new()
{
Tag = new OperationLayerExportHeatMap(),
Icon = new Avalonia.Controls.Image
{
Source = new Bitmap(App.GetAsset("/Assets/Icons/file-image-16x16.png"))
}
},
};
#region DataSets
#endregion
#endregion
#region Members
public Stopwatch LastStopWatch = new();
private bool _isGUIEnabled = true;
private uint _savesCount;
private bool _canSave;
private MenuItem[] _menuFileConvertItems;
private PointerEventArgs _globalPointerEventArgs;
private PointerPoint _globalPointerPoint;
private KeyModifiers _globalModifiers = KeyModifiers.None;
private TabItem _selectedTabItem;
private TabItem _lastSelectedTabItem;
#endregion
#region GUI Models
public bool IsGUIEnabled
{
get => _isGUIEnabled;
set
{
if (!RaiseAndSetIfChanged(ref _isGUIEnabled, value)) return;
if (!_isGUIEnabled)
{
//ProgressWindow = new ProgressWindow();
return;
}
LastStopWatch = Progress.StopWatch;
ProgressFinish();
//ProgressWindow.Close(DialogResults.OK);
//ProgressWindow.Dispose();
/*if (Dispatcher.UIThread.CheckAccess())
{
ProgressWindow.Close();
ProgressWindow.Dispose();
}
else
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ProgressWindow.Close();
ProgressWindow.Dispose();
});
}*/
}
}
public bool IsFileLoaded
{
get => SlicerFile is not null;
set => RaisePropertyChanged();
}
public TabItem TabInformation { get; }
public TabItem TabGCode { get; }
public TabItem TabIssues { get; }
public TabItem TabPixelEditor { get; }
public TabItem TabLog { get; }
public TabItem SelectedTabItem
{
get => _selectedTabItem;
set
{
var lastTab = _selectedTabItem;
if (!RaiseAndSetIfChanged(ref _selectedTabItem, value)) return;
LastSelectedTabItem = lastTab;
if (_firstTimeOnIssues)
{
_firstTimeOnIssues = false;
if (ReferenceEquals(_selectedTabItem, TabIssues) && Settings.Issues.ComputeIssuesOnClickTab)
{
OnClickDetectIssues();
}
}
}
}
public TabItem LastSelectedTabItem
{
get => _lastSelectedTabItem;
set => RaiseAndSetIfChanged(ref _lastSelectedTabItem, value);
}
#endregion
public uint SavesCount
{
get => _savesCount;
set => RaiseAndSetIfChanged(ref _savesCount, value);
}
public bool CanSave
{
get => IsFileLoaded && _canSave;
set => RaiseAndSetIfChanged(ref _canSave, value);
}
public MenuItem[] MenuFileConvertItems
{
get => _menuFileConvertItems;
set => RaiseAndSetIfChanged(ref _menuFileConvertItems, value);
}
#region Constructors
public MainWindow()
{
InitializeComponent();
//App.ThemeSelector?.EnableThemes(this);
InitProgress();
InitInformation();
InitIssues();
InitPixelEditor();
InitClipboardLayers();
InitLayerPreview();
TabInformation = this.FindControl("TabInformation");
TabGCode = this.FindControl("TabGCode");
TabIssues = this.FindControl("TabIssues");
TabPixelEditor = this.FindControl("TabPixelEditor");
TabLog = this.FindControl("TabLog");
foreach (var menuItem in new[] { MenuTools, MenuCalibration, LayerActionsMenu })
{
foreach (var menuTool in menuItem)
{
if (menuTool.Tag is not Operation operation) continue;
menuTool.Header = operation.Title;
menuTool.Click += async (sender, args) => await ShowRunOperation(operation.GetType());
}
}
/*LayerSlider.PropertyChanged += (sender, args) =>
{
Debug.WriteLine(args.Property.Name);
if (args.Property.Name == nameof(LayerSlider.Value))
{
LayerNavigationTooltipPanel.Margin = LayerNavigationTooltipMargin;
return;
}
};*/
//PropertyChanged += OnPropertyChanged;
UpdateTitle();
var clientSizeObs = this.GetObservable(ClientSizeProperty);
clientSizeObs.Subscribe(size => UpdateLayerTrackerHighlightIssues());
var windowStateObs = this.GetObservable(WindowStateProperty);
windowStateObs.Subscribe(windowsState => UpdateLayerTrackerHighlightIssues());
DataContext = this;
AddHandler(DragDrop.DropEvent, (sender, e) =>
{
ProcessFiles(e.Data.GetFileNames().ToArray());
});
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
var windowSize = this.GetScreenWorkingArea();
if (Settings.General.StartMaximized
|| ClientSize.Width > windowSize.Width
|| ClientSize.Height > windowSize.Height)
{
WindowState = WindowState.Maximized;
}
AddLog($"{About.Software} start", Program.ProgramStartupTime.Elapsed.TotalSeconds);
if (Settings.General.CheckForUpdatesOnStartup)
{
Task.Factory.StartNew(VersionChecker.Check);
}
ProcessFiles(Program.Args);
if (!IsFileLoaded && Settings.General.LoadDemoFileOnStartup)
{
ProcessFile(Path.Combine(App.ApplicationPath, About.DemoFile));
}
DispatcherTimer.Run(() =>
{
UpdateTitle();
return true;
}, TimeSpan.FromSeconds(1));
Program.ProgramStartupTime.Stop();
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine(e.PropertyName);
/*if (e.PropertyName == nameof(ActualLayer))
{
LayerSlider.Value = ActualLayer;
ShowLayer();
return;
}*/
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#endregion
#region Overrides
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
_globalPointerEventArgs = e;
_globalModifiers = e.KeyModifiers;
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
_globalPointerPoint = e.GetCurrentPoint(this);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
_globalPointerPoint = e.GetCurrentPoint(this);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
_globalModifiers = e.KeyModifiers;
if (e.Handled
|| !IsFileLoaded
|| LayerImageBox.IsPanning
|| LayerImageBox.TrackerImage is not null
|| LayerImageBox.Cursor == StaticControls.CrossCursor
|| LayerImageBox.Cursor == StaticControls.HandCursor
|| LayerImageBox.SelectionMode == AdvancedImageBox.SelectionModes.Rectangle
) return;
var imageBoxMousePosition = _globalPointerEventArgs?.GetPosition(LayerImageBox) ?? new Point(-1, -1);
if (imageBoxMousePosition.X < 0 || imageBoxMousePosition.Y < 0) return;
// Pixel Edit is active, Shift is down, and the cursor is over the image region.
if (e.KeyModifiers == KeyModifiers.Shift)
{
if (IsPixelEditorActive)
{
LayerImageBox.AutoPan = false;
LayerImageBox.Cursor = StaticControls.CrossCursor;
TooltipOverlayText = "Pixel editing is on (SHIFT):\n" +
"» Click over a pixel to draw\n" +
"» Hold CTRL to clear pixels";
UpdatePixelEditorCursor();
}
else
{
LayerImageBox.Cursor = StaticControls.CrossCursor;
LayerImageBox.SelectionMode = AdvancedImageBox.SelectionModes.Rectangle;
TooltipOverlayText = "ROI & Mask selection mode (SHIFT):\n" +
"» Left-click drag to select a fixed ROI region\n" +
"» Left-click + ALT drag to select specific objects ROI\n" +
"» Right-click on a specific object to select it ROI\n" +
"» Right-click + ALT on a specific object to select it as a Mask\n" +
"Press Esc to clear the ROI & Masks";
}
IsTooltipOverlayVisible = Settings.LayerPreview.TooltipOverlay;
e.Handled = true;
return;
}
if (e.KeyModifiers == KeyModifiers.Control)
{
LayerImageBox.Cursor = StaticControls.HandCursor;
LayerImageBox.AutoPan = false;
TooltipOverlayText = "Issue selection mode:\n" +
"» Click over an issue to select it";
IsTooltipOverlayVisible = Settings.LayerPreview.TooltipOverlay;
e.Handled = true;
return;
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
_globalModifiers = e.KeyModifiers;
if ((e.Key is Key.LeftShift or Key.RightShift || (e.KeyModifiers & KeyModifiers.Shift) != 0) &&
(e.KeyModifiers & KeyModifiers.Control) != 0 &&
e.Key == Key.Z)
{
e.Handled = true;
ClipboardUndo(true);
return;
}
if (e.Key is Key.LeftShift or Key.RightShift
|| (e.KeyModifiers & KeyModifiers.Shift) == 0 || (e.KeyModifiers & KeyModifiers.Control) == 0)
{
LayerImageBox.TrackerImage = null;
LayerImageBox.Cursor = StaticControls.ArrowCursor;
LayerImageBox.AutoPan = true;
LayerImageBox.SelectionMode = AdvancedImageBox.SelectionModes.None;
IsTooltipOverlayVisible = false;
e.Handled = true;
return;
}
base.OnKeyUp(e);
}
public void OpenContextMenu(string name)
{
var menu = this.FindControl($"{name}ContextMenu");
if (menu is null) return;
var parent = this.FindControl