402 lines
13 KiB
C#
402 lines
13 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.Markup.Xaml;
|
|
using Avalonia.Platform.Storage;
|
|
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Media;
|
|
|
|
namespace NotePad
|
|
{
|
|
public partial class MainWindow : Window
|
|
{
|
|
private string? _currentFilePath;
|
|
private string? _currentFileName;
|
|
private bool _isModified;
|
|
private string? _originalText;
|
|
|
|
public MainWindow() : this(null)
|
|
{
|
|
}
|
|
|
|
public MainWindow(string? fileToOpen)
|
|
{
|
|
InitializeComponent();
|
|
UpdateTitle();
|
|
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
textBox.TextChanged += OnTextChanged;
|
|
|
|
Closing += OnWindowClosing;
|
|
|
|
if (!string.IsNullOrEmpty(fileToOpen) && File.Exists(fileToOpen))
|
|
{
|
|
_ = LoadFileAsync(fileToOpen);
|
|
}
|
|
}
|
|
|
|
private async Task LoadFileAsync(string filePath)
|
|
{
|
|
try
|
|
{
|
|
var text = await File.ReadAllTextAsync(filePath);
|
|
_currentFilePath = filePath;
|
|
_currentFileName = Path.GetFileName(filePath);
|
|
|
|
this.FindControl<TextBox>("EditorTextBox")!.Text = text;
|
|
_originalText = text;
|
|
_isModified = false;
|
|
UpdateTitle();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Silently ignore file loading errors on startup
|
|
}
|
|
}
|
|
|
|
private void UpdateTitle()
|
|
{
|
|
var modified = _isModified ? "*" : "";
|
|
Title = $"{modified}{_currentFileName ?? "Untitled"} - Notepad";
|
|
}
|
|
|
|
private void OnTextChanged(object? sender, EventArgs e)
|
|
{
|
|
var currentText = this.FindControl<TextBox>("EditorTextBox")!.Text ?? string.Empty;
|
|
_isModified = currentText != _originalText;
|
|
UpdateTitle();
|
|
}
|
|
|
|
private async void OnWindowClosing(object? sender, WindowClosingEventArgs e)
|
|
{
|
|
if (_isModified)
|
|
{
|
|
e.Cancel = true;
|
|
var result = await ShowSavePrompt();
|
|
|
|
if (result == SavePromptResult.Save)
|
|
{
|
|
await PerformSave();
|
|
if (!_isModified) // Only close if save succeeded
|
|
{
|
|
Closing -= OnWindowClosing;
|
|
Close();
|
|
}
|
|
}
|
|
else if (result == SavePromptResult.DontSave)
|
|
{
|
|
Closing -= OnWindowClosing;
|
|
Close();
|
|
}
|
|
// Cancel = do nothing
|
|
}
|
|
}
|
|
|
|
private async Task<SavePromptResult> ShowSavePrompt()
|
|
{
|
|
var dialog = new Window
|
|
{
|
|
Title = "Notepad",
|
|
Width = 400,
|
|
Height = 150,
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
|
CanResize = false
|
|
};
|
|
|
|
var result = SavePromptResult.Cancel;
|
|
|
|
var panel = new StackPanel { Margin = new Thickness(20) };
|
|
|
|
var message = new TextBlock
|
|
{
|
|
Text = $"Do you want to save changes to {_currentFileName ?? "Untitled"}?",
|
|
TextWrapping = TextWrapping.Wrap,
|
|
Margin = new Thickness(0, 0, 0, 20)
|
|
};
|
|
|
|
var buttonPanel = new StackPanel
|
|
{
|
|
Orientation = Avalonia.Layout.Orientation.Horizontal,
|
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
|
Spacing = 10
|
|
};
|
|
|
|
var yesButton = new Button { Content = "Yes", Width = 80 };
|
|
yesButton.Click += (s, e) => { result = SavePromptResult.Save; dialog.Close(); };
|
|
|
|
var noButton = new Button { Content = "No", Width = 80 };
|
|
noButton.Click += (s, e) => { result = SavePromptResult.DontSave; dialog.Close(); };
|
|
|
|
var cancelButton = new Button { Content = "Cancel", Width = 80 };
|
|
cancelButton.Click += (s, e) => { result = SavePromptResult.Cancel; dialog.Close(); };
|
|
|
|
buttonPanel.Children.Add(yesButton);
|
|
buttonPanel.Children.Add(noButton);
|
|
buttonPanel.Children.Add(cancelButton);
|
|
|
|
panel.Children.Add(message);
|
|
panel.Children.Add(buttonPanel);
|
|
|
|
dialog.Content = panel;
|
|
|
|
await dialog.ShowDialog(this);
|
|
return result;
|
|
}
|
|
|
|
private enum SavePromptResult
|
|
{
|
|
Save,
|
|
DontSave,
|
|
Cancel
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
AvaloniaXamlLoader.Load(this);
|
|
}
|
|
|
|
// New File
|
|
private async void OnNewClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (_isModified)
|
|
{
|
|
var result = await ShowSavePrompt();
|
|
if (result == SavePromptResult.Save)
|
|
{
|
|
await PerformSave();
|
|
if (_isModified) return; // Save was cancelled
|
|
}
|
|
else if (result == SavePromptResult.Cancel)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.FindControl<TextBox>("EditorTextBox")!.Text = string.Empty;
|
|
_currentFilePath = null;
|
|
_currentFileName = null;
|
|
_originalText = string.Empty;
|
|
_isModified = false;
|
|
UpdateTitle();
|
|
}
|
|
|
|
// Open File
|
|
private async void OnOpenClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (_isModified)
|
|
{
|
|
var result = await ShowSavePrompt();
|
|
if (result == SavePromptResult.Save)
|
|
{
|
|
await PerformSave();
|
|
if (_isModified) return; // Save was cancelled
|
|
}
|
|
else if (result == SavePromptResult.Cancel)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
var topLevel = GetTopLevel(this);
|
|
if (topLevel == null) return;
|
|
|
|
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
{
|
|
Title = "Open",
|
|
AllowMultiple = false,
|
|
FileTypeFilter = new[] { FilePickerFileTypes.TextPlain, FilePickerFileTypes.All }
|
|
});
|
|
|
|
if (files?.Count > 0)
|
|
{
|
|
var file = files[0];
|
|
_currentFilePath = file.TryGetLocalPath();
|
|
_currentFileName = file.Name;
|
|
|
|
await using var stream = await file.OpenReadAsync();
|
|
using var reader = new StreamReader(stream);
|
|
var text = await reader.ReadToEndAsync();
|
|
|
|
this.FindControl<TextBox>("EditorTextBox")!.Text = text;
|
|
_originalText = text;
|
|
_isModified = false;
|
|
UpdateTitle();
|
|
}
|
|
}
|
|
|
|
// Save File (if path exists, else Save As)
|
|
private async void OnSaveClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
await PerformSave();
|
|
}
|
|
|
|
private async Task PerformSave()
|
|
{
|
|
if (string.IsNullOrEmpty(_currentFilePath))
|
|
{
|
|
await SaveAs();
|
|
}
|
|
else
|
|
{
|
|
await SaveToPath(_currentFilePath);
|
|
}
|
|
}
|
|
|
|
// Save As
|
|
private async void OnSaveAsClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
await SaveAs();
|
|
}
|
|
|
|
private async Task SaveAs()
|
|
{
|
|
var topLevel = GetTopLevel(this);
|
|
if (topLevel == null) return;
|
|
|
|
var file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
|
{
|
|
Title = "Save As",
|
|
DefaultExtension = "txt",
|
|
FileTypeChoices = new[] { FilePickerFileTypes.TextPlain, FilePickerFileTypes.All },
|
|
SuggestedFileName = _currentFileName ?? "Untitled.txt"
|
|
});
|
|
|
|
if (file != null)
|
|
{
|
|
_currentFilePath = file.TryGetLocalPath();
|
|
_currentFileName = file.Name;
|
|
await SaveToPath(_currentFilePath!);
|
|
UpdateTitle();
|
|
}
|
|
}
|
|
|
|
private async Task SaveToPath(string path)
|
|
{
|
|
var text = this.FindControl<TextBox>("EditorTextBox")!.Text ?? string.Empty;
|
|
await File.WriteAllTextAsync(path, text);
|
|
_originalText = text;
|
|
_isModified = false;
|
|
UpdateTitle();
|
|
}
|
|
|
|
// Exit
|
|
private void OnExitClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
// Word Wrap Toggle
|
|
private void OnWordWrapToggle(object? sender, RoutedEventArgs e)
|
|
{
|
|
var checkBox = this.FindControl<CheckBox>("WordWrapCheckBox")!;
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
|
|
checkBox.IsChecked = !checkBox.IsChecked;
|
|
textBox.TextWrapping = checkBox.IsChecked == true ? TextWrapping.Wrap : TextWrapping.NoWrap;
|
|
}
|
|
|
|
// Edit Menu
|
|
private void OnUndoClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
textBox.Undo();
|
|
}
|
|
|
|
private void OnCutClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
textBox.Cut();
|
|
}
|
|
|
|
private void OnCopyClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
textBox.Copy();
|
|
}
|
|
|
|
private void OnPasteClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
textBox.Paste();
|
|
}
|
|
|
|
private void OnDeleteClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
var selectionStart = textBox.SelectionStart;
|
|
var selectionEnd = textBox.SelectionEnd;
|
|
|
|
if (selectionStart != selectionEnd)
|
|
{
|
|
var text = textBox.Text ?? string.Empty;
|
|
textBox.Text = text.Remove(selectionStart, selectionEnd - selectionStart);
|
|
textBox.SelectionStart = selectionStart;
|
|
textBox.SelectionEnd = selectionStart;
|
|
}
|
|
}
|
|
|
|
private void OnSelectAllClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
textBox.SelectAll();
|
|
}
|
|
|
|
private void OnTimeDateClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var textBox = this.FindControl<TextBox>("EditorTextBox")!;
|
|
var timeDate = DateTime.Now.ToString("h:mm tt M/d/yyyy");
|
|
var position = textBox.SelectionStart;
|
|
var text = textBox.Text ?? string.Empty;
|
|
textBox.Text = text.Insert(position, timeDate);
|
|
textBox.SelectionStart = position + timeDate.Length;
|
|
}
|
|
|
|
// Help Menu
|
|
private async void OnAboutClick(object? sender, RoutedEventArgs e)
|
|
{
|
|
var dialog = new Window
|
|
{
|
|
Title = "About Notepad",
|
|
Width = 350,
|
|
Height = 200,
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
|
CanResize = false
|
|
};
|
|
|
|
var panel = new StackPanel { Margin = new Thickness(20) };
|
|
|
|
var title = new TextBlock
|
|
{
|
|
Text = "Notepad",
|
|
FontSize = 16,
|
|
FontWeight = FontWeight.Bold,
|
|
Margin = new Thickness(0, 0, 0, 10)
|
|
};
|
|
|
|
var info = new TextBlock
|
|
{
|
|
Text = "A simple text editor built with Avalonia",
|
|
TextWrapping = TextWrapping.Wrap,
|
|
Margin = new Thickness(0, 0, 0, 20)
|
|
};
|
|
|
|
var okButton = new Button
|
|
{
|
|
Content = "OK",
|
|
Width = 80,
|
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center
|
|
};
|
|
okButton.Click += (s, e) => dialog.Close();
|
|
|
|
panel.Children.Add(title);
|
|
panel.Children.Add(info);
|
|
panel.Children.Add(okButton);
|
|
|
|
dialog.Content = panel;
|
|
|
|
await dialog.ShowDialog(this);
|
|
}
|
|
}
|
|
} |