/* * 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