Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RegPreview] Various improvements on how files are saved #37628

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public sealed partial class RegistryPreviewMainPage : Page
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();

// Indicator if we loaded/reloaded/saved a file and need to skip TextChanged event one time.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Indicator if we loaded/reloaded/saved a file and need to skip TextChanged event one time.
// Indicator if we loaded/reloaded/saved a file and need to skip TextChanged event one time.
// (Solves the problem that enabling the event handler fires it one time.)

private static bool editorContentChangedScripted;

/// <summary>
/// Event that is will prevent the app from closing if the "save file" flag is active
/// </summary>
Expand Down Expand Up @@ -107,11 +110,17 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
SaveFile();
if (!AskFileName(string.Empty) ||
!SaveFile())
{
return;
}

break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
saveButton.IsEnabled = false;
UpdateUnsavedFileIndicator(false);
break;
default:
// Don't open the new file!
Expand All @@ -138,11 +147,15 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
editorContentChangedScripted = true;

// update file name
_appFileName = storageFile.Path;
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));

// disable the Save button as it's a new file
saveButton.IsEnabled = false;
UpdateUnsavedFileIndicator(false);

// Restore the event handler as we're loaded
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
Expand All @@ -154,31 +167,33 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
/// </summary>
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
SaveFile();
if (!AskFileName(string.Empty))
{
return;
}

// save and update window title
_ = SaveFile();
}

/// <summary>
/// Uses a picker to save out a copy of the current reg file
/// </summary>
private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
{
// Save out a new REG file and then open it - we have to use the direct Win32 method because FileOpenPicker crashes when it's
// called while running as admin
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow);
string filename = SaveFilePicker.ShowDialog(
windowHandle,
resourceLoader.GetString("SuggestFileName"),
resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0',
resourceLoader.GetString("SaveDialogTitle"));
// mute the TextChanged handler to make for clean UI
editorContentChangedScripted = true;
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;

if (filename == string.Empty)
if (!AskFileName(_appFileName) || !SaveFile())
{
return;
}

_appFileName = filename;
SaveFile();
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));

// restore the TextChanged handler
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
}

/// <summary>
Expand All @@ -187,12 +202,14 @@ private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
editorContentChangedScripted = true;
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;

// reload the current Registry file and update the toolbar accordingly.
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true);

saveButton.IsEnabled = false;
UpdateUnsavedFileIndicator(false);

// restore the TextChanged handler
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
Expand Down Expand Up @@ -257,7 +274,12 @@ private async void WriteButton_Click(object sender, RoutedEventArgs e)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
SaveFile();
if (!AskFileName(string.Empty) ||
!SaveFile())
{
return;
}

break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
Expand Down Expand Up @@ -354,7 +376,13 @@ private void MonacoEditor_TextChanged(object sender, EventArgs e)
_dispatcherQueue.TryEnqueue(() =>
{
RefreshRegistryFile();
saveButton.IsEnabled = true;
if (!editorContentChangedScripted)
{
saveButton.IsEnabled = true;
UpdateUnsavedFileIndicator(true);
}

editorContentChangedScripted = false;
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Input;
Expand All @@ -22,6 +23,9 @@ namespace RegistryPreviewUILib
{
public sealed partial class RegistryPreviewMainPage : Page
{
private static readonly string _unsavedFileIndicator = "* ";
private static readonly char[] _unsavedFileIndicatorChars = [' ', '*'];

private static SemaphoreSlim _dialogSemaphore = new(1);
private string lastKeyPath;

Expand Down Expand Up @@ -830,10 +834,16 @@ private async void HandleDirtyClosing(string title, string content, string prima
{
case ContentDialogResult.Primary:
// Save, then close
SaveFile();
if (!AskFileName(string.Empty) ||
!SaveFile())
{
return;
}

break;
case ContentDialogResult.Secondary:
// Don't save, and then close!
UpdateUnsavedFileIndicator(false);
saveButton.IsEnabled = false;
break;
default:
Expand Down Expand Up @@ -902,11 +912,63 @@ public void ChangeCursor(UIElement uiElement, bool wait)
type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }, CultureInfo.InvariantCulture);
}

public void UpdateUnsavedFileIndicator(bool show)
{
// get and cut current title
string currentTitle = Regex.Replace(_mainWindow.Title, APPNAME + @"$|\s-\s" + APPNAME + @"$", string.Empty);

// verify
bool titleContainsIndicator = currentTitle.StartsWith(_unsavedFileIndicator, StringComparison.CurrentCultureIgnoreCase);

// update
if (!titleContainsIndicator && show)
{
_updateWindowTitleFunction(_unsavedFileIndicator + currentTitle);
}
else if (titleContainsIndicator && !show)
{
_updateWindowTitleFunction(currentTitle.TrimStart(_unsavedFileIndicatorChars));
}
}

/// <summary>
/// Ask the user for the file path if it is unknown because of an unsaved file
/// </summary>
/// <param name="fileName">If not empty always ask for a file path and use the value as name.</param>
/// <returns>Returns true if user selected a path, otherwise false</returns>
public bool AskFileName(string fileName)
{
if (string.IsNullOrEmpty(_appFileName) || !string.IsNullOrEmpty(fileName) )
{
string fName = string.IsNullOrEmpty(fileName) ? resourceLoader.GetString("SuggestFileName") : fileName;

// Save out a new REG file and then open it - we have to use the direct Win32 method because FileOpenPicker crashes when it's
// called while running as admin
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow);
string filename = SaveFilePicker.ShowDialog(
windowHandle,
fName,
resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0',
resourceLoader.GetString("SaveDialogTitle"));

if (filename == string.Empty)
{
return false;
}

_appFileName = filename;
}

return true;
}

/// <summary>
/// Wrapper method that saves the current file in place, using the current text in editor.
/// </summary>
private void SaveFile()
private bool SaveFile()
{
bool saveSuccess = true;

ChangeCursor(gridPreview, true);

// set up the FileStream for all writing
Expand All @@ -930,10 +992,13 @@ private void SaveFile()
streamWriter.Close();

// only change when the save is successful
_updateWindowTitleFunction(_appFileName);
saveButton.IsEnabled = false;
}
catch (UnauthorizedAccessException ex)
{
saveSuccess = false;

// this exception is thrown if the file is there but marked as read only
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
Expand All @@ -942,6 +1007,8 @@ private void SaveFile()
}
catch
{
saveSuccess = false;

// this catch handles all other exceptions thrown when trying to write the file out
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
Expand All @@ -959,6 +1026,8 @@ private void SaveFile()

// restore the cursor
ChangeCursor(gridPreview, false);

return saveSuccess;
}

/// <summary>
Expand Down