204 lines
7.3 KiB
C#
204 lines
7.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |