Files
markus/src/Views/MainWindow.axaml.cs

204 lines
7.3 KiB
C#
Raw Normal View History

2026-01-15 11:11:42 -06:00
using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using AvaloniaEdit;
using MarkdownEditor.Constants;
using MarkdownEditor.ViewModels;
namespace MarkdownEditor.Views;
/// <summary>
/// Main application window.
/// Handles initialization and coordination between the view and view model.
/// </summary>
public partial class MainWindow : Window
{
private bool _isClosingConfirmed = false;
/// <summary>
/// Initializes a new instance of the <see cref="MainWindow"/> class.
/// </summary>
public MainWindow()
{
InitializeComponent();
// Subscribe to pointer pressed to capture selection before command executes
this.AddHandler(Button.PointerPressedEvent, OnButtonPointerPressed, Avalonia.Interactivity.RoutingStrategies.Tunnel);
}
/// <inheritdoc/>
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
if (DataContext is MainWindowViewModel viewModel)
{
viewModel.StorageProvider = StorageProvider;
// Restore session after StorageProvider is set
_ = viewModel.RestoreSessionAsync();
}
}
/// <inheritdoc/>
protected override async void OnClosing(WindowClosingEventArgs e)
{
if (DataContext is not MainWindowViewModel viewModel)
{
base.OnClosing(e);
return;
}
// If we've already confirmed the close (from a previous call), just save session and close
if (_isClosingConfirmed)
{
await viewModel.SaveSessionAsync();
base.OnClosing(e);
return;
}
// Check for unsaved changes
if (viewModel.HasUnsavedChanges())
{
// Cancel the close event to prevent immediate closing
e.Cancel = true;
// Show save confirmation dialog
var unsavedCount = viewModel.GetUnsavedChangesCount();
var message = unsavedCount == 1
? AppConstants.Messages.SaveChangesBeforeClose
: string.Format(AppConstants.Messages.SaveChangesBeforeCloseMultiple, unsavedCount);
var dialog = new SaveConfirmationDialog(message);
await dialog.ShowDialog<bool>(this);
var result = dialog.Result;
switch (result)
{
case SaveConfirmationResult.Save:
// User wants to save - save all unsaved changes
var saved = await viewModel.SaveAllUnsavedChangesAsync();
if (saved)
{
// All files saved successfully - mark as confirmed and close
_isClosingConfirmed = true;
await viewModel.SaveSessionAsync();
Close();
}
// If save was cancelled, keep the window open (e.Cancel is already true)
// Don't call base.OnClosing since we're cancelling
return;
case SaveConfirmationResult.DontSave:
// User doesn't want to save - mark as confirmed and close
_isClosingConfirmed = true;
await viewModel.SaveSessionAsync();
Close();
// Don't call base.OnClosing here - Close() will trigger OnClosing again
return;
case SaveConfirmationResult.Cancel:
default:
// User cancelled - keep the window open (e.Cancel is already true)
// Don't call base.OnClosing since we're cancelling
return;
}
}
else
{
// No unsaved changes - just save session and close normally
await viewModel.SaveSessionAsync();
}
base.OnClosing(e);
}
/// <summary>
/// Handles pointer pressed events to capture text selection before formatting commands execute.
/// This is necessary because toolbar buttons take focus, losing the text selection.
/// </summary>
private void OnButtonPointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e)
{
// Walk up the visual tree to find the Button
Button? button = null;
if (e.Source is Control sourceControl)
{
button = sourceControl.GetVisualAncestors().OfType<Button>().FirstOrDefault();
}
// Only process toolbar buttons
if (button != null && button.Classes.Contains("toolbar-btn"))
{
// Find the TextEditor and update selection in ViewModel before command executes
if (DataContext is MainWindowViewModel mainViewModel && mainViewModel.SelectedTab != null)
{
var textEditor = this.GetVisualDescendants().OfType<TextEditor>()
.FirstOrDefault(te => te.IsVisible);
if (textEditor != null)
{
mainViewModel.SelectedTab.SelectionStart = textEditor.SelectionStart;
mainViewModel.SelectedTab.SelectionEnd = textEditor.SelectionStart + textEditor.SelectionLength;
}
}
}
}
/// <summary>
/// Handles the Insert Hyperlink button click.
/// </summary>
private async void OnInsertHyperlink(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is MarkdownTabViewModel tabViewModel)
{
CaptureSelection();
var dialog = new UrlInputDialog("Insert Hyperlink", "Enter the URL for the link:");
var result = await dialog.ShowDialog<bool>(this);
if (dialog.IsConfirmed && !string.IsNullOrWhiteSpace(dialog.Url))
{
tabViewModel.InsertHyperlink(dialog.Url);
}
}
}
/// <summary>
/// Handles the Insert Image button click.
/// </summary>
private async void OnInsertImage(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is MarkdownTabViewModel tabViewModel)
{
CaptureSelection();
var dialog = new UrlInputDialog("Insert Image", "Enter the URL for the image:");
var result = await dialog.ShowDialog<bool>(this);
if (dialog.IsConfirmed && !string.IsNullOrWhiteSpace(dialog.Url))
{
tabViewModel.InsertImage(dialog.Url);
}
}
}
/// <summary>
/// Captures the current text selection from the visible text editor.
/// </summary>
private void CaptureSelection()
{
if (DataContext is MainWindowViewModel mainViewModel && mainViewModel.SelectedTab != null)
{
var textEditor = this.GetVisualDescendants().OfType<TextEditor>()
.FirstOrDefault(te => te.IsVisible);
if (textEditor != null)
{
mainViewModel.SelectedTab.SelectionStart = textEditor.SelectionStart;
mainViewModel.SelectedTab.SelectionEnd = textEditor.SelectionStart + textEditor.SelectionLength;
}
}
}
}