/* * 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.Net.Http; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using UVtools.AvaloniaControls; using UVtools.Core; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Layers; using UVtools.Core.Managers; using UVtools.Core.Network; using UVtools.Core.Objects; using UVtools.Core.Operations; using UVtools.Core.Suggestions; using UVtools.Core.SystemOS; 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/expand-alt-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 OperationFadeExposureTime(), Icon = new Avalonia.Controls.Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/history-16x16.png")) } }, new() { Tag = new OperationDoubleExposure(), Icon = new Avalonia.Controls.Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/equals-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 OperationDynamicLayerHeight(), Icon = new Avalonia.Controls.Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/dynamic-layers-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")) } }, new() { Tag = new OperationLayerExportMesh(), Icon = new Avalonia.Controls.Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/cubes-16x16.png")) } }, }; #region DataSets #endregion #endregion #region Members public Stopwatch LastStopWatch = new(); private bool _isGUIEnabled = true; private uint _savesCount; private bool _canSave; private MenuItem _menuFileSendTo; private MenuItem[] _menuFileOpenRecentItems; private MenuItem[] _menuFileSendToItems; 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[] MenuFileOpenRecentItems { get => _menuFileOpenRecentItems; set => RaiseAndSetIfChanged(ref _menuFileOpenRecentItems, value); } public MenuItem[] MenuFileSendToItems { get => _menuFileSendToItems; set => RaiseAndSetIfChanged(ref _menuFileSendToItems, 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(); InitSuggestions(); RefreshRecentFiles(true); 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()); }); _menuFileSendTo = this.FindControl("MainMenu.File.SendTo"); this.FindControl("MainMenu.File").SubmenuOpened += (sender, e) => { if (!IsFileLoaded) return; var menuItems = new List(); var drives = DriveInfo.GetDrives(); if (drives.Length > 0) { foreach (var drive in drives) { if (drive.DriveType != DriveType.Removable || !drive.IsReady) continue; // Not our target, skip if (SlicerFile.FileFullPath.StartsWith(drive.Name)) continue; // File already on this device, skip var header = drive.Name; if (!string.IsNullOrWhiteSpace(drive.VolumeLabel)) { header += $" {drive.VolumeLabel}"; } header += $" ({SizeExtensions.SizeSuffix(drive.AvailableFreeSpace)}) [{drive.DriveFormat}]"; var menuItem = new MenuItem { Header = header, Tag = drive, Icon = new Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/usb-16x16.png")) } }; menuItem.Click += FileSendToItemClick; menuItems.Add(menuItem); } } if (Settings.General.SendToCustomLocations is not null && Settings.General.SendToCustomLocations.Count > 0) { foreach (var location in Settings.General.SendToCustomLocations) { if(!location.IsEnabled) continue; if (!string.IsNullOrWhiteSpace(location.CompatibleExtensions)) { var extensions = location.CompatibleExtensions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var found = false; foreach (var ext in extensions) { found = SlicerFile.FileFullPath.EndsWith($".{ext}"); if (found) break; } if(!found) continue; } var menuItem = new MenuItem { Header = location.ToString(), Tag = location, Icon = new Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/folder-16x16.png")) } }; menuItem.Click += FileSendToItemClick; menuItems.Add(menuItem); } } if (Settings.Network.RemotePrinters is not null && Settings.Network.RemotePrinters.Count > 0) { foreach (var remotePrinter in Settings.Network.RemotePrinters) { if(!remotePrinter.IsEnabled || !remotePrinter.IsValid) continue; if (!string.IsNullOrWhiteSpace(remotePrinter.CompatibleExtensions)) { var extensions = remotePrinter.CompatibleExtensions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var found = false; foreach (var ext in extensions) { found = SlicerFile.FileFullPath.EndsWith($".{ext}"); if (found) break; } if (!found) continue; } var menuItem = new MenuItem { Header = remotePrinter.ToString(), Tag = remotePrinter, Icon = new Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/network-wired-16x16.png")) } }; menuItem.Click += FileSendToItemClick; menuItems.Add(menuItem); } } if (Settings.General.SendToProcess is not null && Settings.General.SendToProcess.Count > 0) { foreach (var application in Settings.General.SendToProcess) { if (!application.IsEnabled ) continue; if (!string.IsNullOrWhiteSpace(application.CompatibleExtensions)) { var extensions = application.CompatibleExtensions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var found = false; foreach (var ext in extensions) { found = SlicerFile.FileFullPath.EndsWith($".{ext}"); if (found) break; } if (!found) continue; } var menuItem = new MenuItem { Header = application.Name, Tag = application, Icon = new Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/cog-16x16.png")) } }; menuItem.Click += FileSendToItemClick; menuItems.Add(menuItem); } } MenuFileSendToItems = menuItems.ToArray(); _menuFileSendTo.IsVisible = _menuFileSendTo.IsEnabled = menuItems.Count > 0; }; } private async void FileSendToItemClick(object? sender, RoutedEventArgs e) { if (sender is not MenuItem menuItem) return; string path; if (menuItem.Tag is DriveInfo drive) { if (!drive.IsReady) { await this.MessageBoxError($"The device {drive.Name} is not ready/available at this time.", "Unable to copy the file"); return; } path = drive.Name; } else if (menuItem.Tag is MappedDevice device) { path = device.Path; } else if (menuItem.Tag is RemotePrinter preRemotePrinter) { path = preRemotePrinter.HostUrl; } else if (menuItem.Tag is MappedProcess process) { path = process.Name; } else { return; } if (CanSave) { switch (await this.MessageBoxQuestion("There are unsaved changes. Do you want to save the current file before copy it over?\n\n" + "Yes: Save the current file and copy it over.\n" + "No: Copy the file without current modifications.\n" + "Cancel: Abort the operation.", "Send to - Unsaved changes", ButtonEnum.YesNoCancel)) { case ButtonResult.Yes: await SaveFile(true); break; case ButtonResult.No: break; default: return; } } ShowProgressWindow($"Sending: {SlicerFile.Filename} to {path}"); Progress.ItemName = "Sending"; if (menuItem.Tag is RemotePrinter remotePrinter) { var startPrint = (_globalModifiers & KeyModifiers.Shift) != 0 && remotePrinter.RequestPrintFile is not null && remotePrinter.RequestPrintFile.IsValid; if (startPrint) { if (await this.MessageBoxQuestion( "If supported, you are about to send the file and start the print with it.\n" + "Keep in mind there is no guarantee that the file will start to print.\n" + "Are you sure you want to continue?\n\n" + "Yes: Send file and print it.\n" + "No: Cancel file sending and print.", "Send and print the file?") != ButtonResult.Yes) return; } HttpResponseMessage response = null; try { await using var stream = File.OpenRead(SlicerFile.FileFullPath); using var httpContent = new StreamContent(stream); Progress.ItemCount = (uint)(stream.Length / 1000000); bool isCopying = true; try { var task = new Task(() => { while (isCopying) { Progress.ProcessedItems = (uint)(stream.Position / 1000000); Thread.Sleep(200); } }); } catch (Exception) { // ignored } response = await remotePrinter.RequestUploadFile.SendRequest(remotePrinter, Progress, SlicerFile.Filename, httpContent); isCopying = false; if (!response.IsSuccessStatusCode) { await this.MessageBoxError(response.ToString(), "Send to printer"); } } catch (OperationCanceledException) { } catch (Exception ex) { await this.MessageBoxError(ex.Message, "Send to printer"); } if ( response is not null && response.IsSuccessStatusCode && startPrint) { response.Dispose(); Progress.Title = "Waiting 2 seconds..."; await Task.Delay(2000); try { response = await remotePrinter.RequestPrintFile.SendRequest(remotePrinter, Progress, SlicerFile.Filename); if (!response.IsSuccessStatusCode) { await this.MessageBoxError(response.ToString(), "Unable to send the print command"); } /*else { await this.MessageBoxInfo(response.ToString(), "Print send command report"); }*/ } catch (OperationCanceledException) { } catch (Exception ex) { await this.MessageBoxError(ex.Message, "Unable to send the print command"); } } } else if (menuItem.Tag is MappedProcess process) { Progress.ItemName = "Waiting for completion"; try { await process.StartProcess(SlicerFile, Progress.Token); } catch (OperationCanceledException){} catch (Exception ex) { await this.MessageBoxError(ex.Message, $"Unable to start the process {process.Name}"); } } else { /*var copyResult = await Task.Factory.StartNew(() => { try { var fileDest = Path.Combine(path, SlicerFile.Filename); //File.Copy(SlicerFile.FileFullPath, $"{Path.Combine(path, SlicerFile.Filename)}", true); var buffer = new byte[1024 * 1024]; // 1MB buffer using var source = File.OpenRead(SlicerFile.FileFullPath); using var dest = new FileStream(fileDest, FileMode.Create, FileAccess.Write); //long totalBytes = 0; //int currentBlockSize; Progress.Reset("Megabyte(s)", (uint)(source.Length / 1000000)); var copyProgress = new Progress(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1000000)); source.CopyToAsync(dest, 0, copyProgress, Progress.Token).ConfigureAwait(false); /*while ((currentBlockSize = source.Read(buffer)) > 0) { totalBytes += currentBlockSize; dest.Write(buffer, 0, currentBlockSize); if (Progress.Token.IsCancellationRequested) { // Delete dest file here dest.Dispose(); File.Delete(fileDest); return false; } Progress.ProcessedItems = (uint)(totalBytes / 1000000); }*/ /* return true; } catch (OperationCanceledException) { } catch (Exception exception) { Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(exception.ToString(), "Unable to copy the file")); } return false; });*/ bool copyResult = false; var fileDest = Path.Combine(path, SlicerFile.Filename); try { await using var source = File.OpenRead(SlicerFile.FileFullPath); await using var dest = new FileStream(fileDest, FileMode.Create, FileAccess.Write); Progress.Reset("Megabyte(s)", (uint)(source.Length / 1000000)); var copyProgress = new Progress(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1000000)); await source.CopyToAsync(dest, copyProgress, Progress.Token); copyResult = true; } catch (OperationCanceledException) { try { if (File.Exists(fileDest)) File.Delete(fileDest); } catch (Exception ex) { Debug.WriteLine(ex); } } catch (Exception exception) { await this.MessageBoxError(exception.Message, "Unable to copy the file"); } if(copyResult && menuItem.Tag is DriveInfo removableDrive && OperatingSystem.IsWindows() && Settings.General.SendToPromptForRemovableDeviceEject) { if (await this.MessageBoxQuestion( $"File '{SlicerFile.Filename}' has copied successfully into {removableDrive.Name}\n" + $"Do you want to eject the {removableDrive.Name} drive now?", "Copied ok, eject the drive?") == ButtonResult.Yes) { Progress.ResetAll($"Ejecting {removableDrive.Name}"); var ejectResult = await Task.Factory.StartNew(() => { try { return Core.SystemOS.Windows.USB.USBEject(removableDrive.Name); } catch (OperationCanceledException) { } catch (Exception exception) { Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(exception.Message, $"Unable to eject the drive {removableDrive.Name}")); } return false; }); if (!ejectResult) { await this.MessageBoxError($"Unable to eject the drive {removableDrive.Name}\n\n" + "Possible causes:\n" + "- Drive may be busy or locked\n" + "- Drive was already ejected\n" + "- No permission to eject the drive\n" + "- Another error while trying to eject the drive\n\n" + "Please try to eject the drive manually.", $"Unable to eject the drive {removableDrive.Name}"); } } } } IsGUIEnabled = true; } 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.LoadLastRecentFileOnStartup) { RecentFiles.Load(); if (RecentFiles.Instance.Count > 0) { ProcessFile(Path.Combine(App.ApplicationPath, RecentFiles.Instance[0])); } } 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" + "» Right-click + ALT + CTRL on a specific object to select all it enclosing areas 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