mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-30 08:56:33 +01:00
Compare commits
1 Commits
jay/DarkMo
...
peiyao/tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a284c89a3 |
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
// 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>
|
||||
@@ -77,6 +81,67 @@ namespace RegistryPreviewUILib
|
||||
MonacoEditor.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// New button action: Ask to save last changes and reset editor content to reg header only
|
||||
/// </summary>
|
||||
private async void NewButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Check to see if the current file has been saved
|
||||
if (saveButton.IsEnabled)
|
||||
{
|
||||
ContentDialog contentDialog = new ContentDialog()
|
||||
{
|
||||
Title = resourceLoader.GetString("YesNoCancelDialogTitle"),
|
||||
Content = resourceLoader.GetString("YesNoCancelDialogContent"),
|
||||
PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"),
|
||||
SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"),
|
||||
CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"),
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
};
|
||||
|
||||
// Use this code to associate the dialog to the appropriate AppWindow by setting
|
||||
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
|
||||
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
|
||||
{
|
||||
contentDialog.XamlRoot = this.Content.XamlRoot;
|
||||
}
|
||||
|
||||
ContentDialogResult contentDialogResult = await contentDialog.ShowAsync();
|
||||
switch (contentDialogResult)
|
||||
{
|
||||
case ContentDialogResult.Primary:
|
||||
// Save, then continue the new action
|
||||
if (!AskFileName(string.Empty) ||
|
||||
!SaveFile())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case ContentDialogResult.Secondary:
|
||||
// Don't save and continue the new action!
|
||||
break;
|
||||
default:
|
||||
// Don't open the new action!
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// mute the TextChanged handler to make for clean UI
|
||||
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
|
||||
|
||||
// reset editor, file info and ui.
|
||||
_appFileName = string.Empty;
|
||||
ResetEditorAndFile();
|
||||
|
||||
// disable buttons that do not make sense
|
||||
UpdateUnsavedFileState(false);
|
||||
refreshButton.IsEnabled = false;
|
||||
|
||||
// restore the TextChanged handler
|
||||
ButtonAction_RestoreTextChangedEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a picker to select a new file to open
|
||||
/// </summary>
|
||||
@@ -107,11 +172,15 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
// Don't open the new file!
|
||||
@@ -138,14 +207,16 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
// mute the TextChanged handler to make for clean UI
|
||||
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
|
||||
|
||||
// update file name
|
||||
_appFileName = storageFile.Path;
|
||||
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));
|
||||
|
||||
// disable the Save button as it's a new file
|
||||
saveButton.IsEnabled = false;
|
||||
UpdateUnsavedFileState(false);
|
||||
|
||||
// Restore the event handler as we're loaded
|
||||
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
|
||||
ButtonAction_RestoreTextChangedEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +225,14 @@ namespace RegistryPreviewUILib
|
||||
/// </summary>
|
||||
private void SaveButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveFile();
|
||||
if (!AskFileName(string.Empty))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// save and update window title
|
||||
// error handling and ui update happens in SaveFile() method
|
||||
_ = SaveFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,47 +240,24 @@ namespace RegistryPreviewUILib
|
||||
/// </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
|
||||
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
|
||||
|
||||
if (filename == string.Empty)
|
||||
if (!AskFileName(_appFileName) || !SaveFile())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appFileName = filename;
|
||||
SaveFile();
|
||||
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));
|
||||
|
||||
// restore the TextChanged handler
|
||||
ButtonAction_RestoreTextChangedEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the current REG file from storage
|
||||
/// </summary>
|
||||
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// mute the TextChanged handler to make for clean UI
|
||||
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
|
||||
|
||||
// reload the current Registry file and update the toolbar accordingly.
|
||||
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true);
|
||||
|
||||
// disable the Save button as it's a new file
|
||||
saveButton.IsEnabled = false;
|
||||
|
||||
// restore the TextChanged handler
|
||||
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the editor content
|
||||
/// </summary>
|
||||
private async void NewButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Check to see if the current file has been saved
|
||||
if (saveButton.IsEnabled)
|
||||
@@ -210,10 +265,9 @@ namespace RegistryPreviewUILib
|
||||
ContentDialog contentDialog = new ContentDialog()
|
||||
{
|
||||
Title = resourceLoader.GetString("YesNoCancelDialogTitle"),
|
||||
Content = resourceLoader.GetString("YesNoCancelDialogContent"),
|
||||
PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"),
|
||||
SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"),
|
||||
CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"),
|
||||
Content = resourceLoader.GetString("ReloadDialogContent"),
|
||||
PrimaryButtonText = resourceLoader.GetString("ReloadDialogPrimaryButtonText"),
|
||||
CloseButtonText = resourceLoader.GetString("ReloadDialogCloseButtonText"),
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
};
|
||||
|
||||
@@ -228,15 +282,10 @@ namespace RegistryPreviewUILib
|
||||
switch (contentDialogResult)
|
||||
{
|
||||
case ContentDialogResult.Primary:
|
||||
// Save, then continue the file open
|
||||
SaveFile();
|
||||
break;
|
||||
case ContentDialogResult.Secondary:
|
||||
// Don't save and continue the file open!
|
||||
saveButton.IsEnabled = false;
|
||||
// Don't save and continue the reload action!
|
||||
break;
|
||||
default:
|
||||
// Don't open the new file!
|
||||
// Don't continue the reload action!
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -244,16 +293,14 @@ namespace RegistryPreviewUILib
|
||||
// mute the TextChanged handler to make for clean UI
|
||||
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
|
||||
|
||||
// reset editor, file info and ui.
|
||||
_appFileName = string.Empty;
|
||||
ResetEditorAndFile();
|
||||
// reload the current Registry file and update the toolbar accordingly.
|
||||
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true);
|
||||
|
||||
// disable the Save button as it's a new file
|
||||
UpdateUnsavedFileState(false);
|
||||
|
||||
// restore the TextChanged handler
|
||||
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
|
||||
|
||||
// disable buttons that do not make sense
|
||||
saveButton.IsEnabled = false;
|
||||
refreshButton.IsEnabled = false;
|
||||
ButtonAction_RestoreTextChangedEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -314,15 +361,20 @@ namespace RegistryPreviewUILib
|
||||
switch (contentDialogResult)
|
||||
{
|
||||
case ContentDialogResult.Primary:
|
||||
// Save, then continue the file open
|
||||
SaveFile();
|
||||
// Save, then continue the merge action
|
||||
if (!AskFileName(string.Empty) ||
|
||||
!SaveFile())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case ContentDialogResult.Secondary:
|
||||
// Don't save and continue the file open!
|
||||
saveButton.IsEnabled = false;
|
||||
// Don't save and continue the merge action!
|
||||
UpdateUnsavedFileState(false);
|
||||
break;
|
||||
default:
|
||||
// Don't open the new file!
|
||||
// Don't merge the file!
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -412,8 +464,27 @@ namespace RegistryPreviewUILib
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
RefreshRegistryFile();
|
||||
saveButton.IsEnabled = true;
|
||||
if (!editorContentChangedScripted)
|
||||
{
|
||||
UpdateUnsavedFileState(true);
|
||||
}
|
||||
|
||||
editorContentChangedScripted = false;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets indicator for programatic text change and adds text changed handler
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this always, if button actions temporary disable the text changed event
|
||||
/// </remarks>
|
||||
private void ButtonAction_RestoreTextChangedEvent()
|
||||
{
|
||||
// Solves the problem that enabling the event handler fires it one time.
|
||||
// These one time fired event would causes wrong unsaved changes state.
|
||||
editorContentChangedScripted = true;
|
||||
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Input;
|
||||
@@ -22,6 +23,9 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
public sealed partial class RegistryPreviewMainPage : Page
|
||||
{
|
||||
private static readonly string _unsavedFileIndicator = "* ";
|
||||
private static readonly char[] _unsavedFileIndicatorChars = [' ', '*'];
|
||||
|
||||
private const string NEWFILEHEADER = "Windows Registry Editor Version 5.00\r\n\r\n";
|
||||
|
||||
private static SemaphoreSlim _dialogSemaphore = new(1);
|
||||
@@ -831,42 +835,66 @@ namespace RegistryPreviewUILib
|
||||
/// </summary>
|
||||
private async void HandleDirtyClosing(string title, string content, string primaryButtonText, string secondaryButtonText, string closeButtonText)
|
||||
{
|
||||
ContentDialog contentDialog = new ContentDialog()
|
||||
if (_dialogSemaphore.CurrentCount == 0)
|
||||
{
|
||||
Title = title,
|
||||
Content = content,
|
||||
PrimaryButtonText = primaryButtonText,
|
||||
SecondaryButtonText = secondaryButtonText,
|
||||
CloseButtonText = closeButtonText,
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
};
|
||||
|
||||
// Use this code to associate the dialog to the appropriate AppWindow by setting
|
||||
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
|
||||
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
|
||||
{
|
||||
contentDialog.XamlRoot = this.Content.XamlRoot;
|
||||
return;
|
||||
}
|
||||
|
||||
ContentDialogResult contentDialogResult = await contentDialog.ShowAsync();
|
||||
|
||||
switch (contentDialogResult)
|
||||
try
|
||||
{
|
||||
case ContentDialogResult.Primary:
|
||||
// Save, then close
|
||||
SaveFile();
|
||||
break;
|
||||
case ContentDialogResult.Secondary:
|
||||
// Don't save, and then close!
|
||||
saveButton.IsEnabled = false;
|
||||
break;
|
||||
default:
|
||||
// Cancel closing!
|
||||
return;
|
||||
}
|
||||
await _dialogSemaphore.WaitAsync();
|
||||
|
||||
// if we got here, we should try to close again
|
||||
Application.Current.Exit();
|
||||
ContentDialog contentDialog = new ContentDialog()
|
||||
{
|
||||
Title = title,
|
||||
Content = content,
|
||||
PrimaryButtonText = primaryButtonText,
|
||||
SecondaryButtonText = secondaryButtonText,
|
||||
CloseButtonText = closeButtonText,
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
};
|
||||
|
||||
// Use this code to associate the dialog to the appropriate AppWindow by setting
|
||||
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
|
||||
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
|
||||
{
|
||||
contentDialog.XamlRoot = this.Content.XamlRoot;
|
||||
}
|
||||
|
||||
ContentDialogResult contentDialogResult = await contentDialog.ShowAsync();
|
||||
|
||||
switch (contentDialogResult)
|
||||
{
|
||||
case ContentDialogResult.Primary:
|
||||
// Save, then close
|
||||
if (!AskFileName(string.Empty) ||
|
||||
!SaveFile())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case ContentDialogResult.Secondary:
|
||||
// Don't save, and then close!
|
||||
UpdateUnsavedFileState(false);
|
||||
break;
|
||||
default:
|
||||
// Cancel closing!
|
||||
return;
|
||||
}
|
||||
|
||||
// if we got here, we should try to close again
|
||||
Application.Current.Exit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Normally nothing to catch here.
|
||||
// But for safety the try-catch ensures that we always release the content dialog lock and exit correctly.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dialogSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -926,11 +954,71 @@ namespace RegistryPreviewUILib
|
||||
type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public void UpdateUnsavedFileState(bool unsavedChanges)
|
||||
{
|
||||
// get, cut and analyze the current title
|
||||
string currentTitle = Regex.Replace(_mainWindow.Title, APPNAME + @"$|\s-\s" + APPNAME + @"$", string.Empty);
|
||||
bool titleContainsIndicator = currentTitle.StartsWith(_unsavedFileIndicator, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
// update window title and save button state
|
||||
if (unsavedChanges)
|
||||
{
|
||||
saveButton.IsEnabled = true;
|
||||
|
||||
if (!titleContainsIndicator)
|
||||
{
|
||||
_updateWindowTitleFunction(_unsavedFileIndicator + currentTitle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
saveButton.IsEnabled = false;
|
||||
|
||||
if (titleContainsIndicator)
|
||||
{
|
||||
_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
|
||||
@@ -954,10 +1042,13 @@ namespace RegistryPreviewUILib
|
||||
streamWriter.Close();
|
||||
|
||||
// only change when the save is successful
|
||||
saveButton.IsEnabled = false;
|
||||
UpdateUnsavedFileState(false);
|
||||
_updateWindowTitleFunction(_appFileName);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
saveSuccess = false;
|
||||
|
||||
// this exception is thrown if the file is there but marked as read only
|
||||
ShowMessageBox(
|
||||
resourceLoader.GetString("ErrorDialogTitle"),
|
||||
@@ -966,6 +1057,8 @@ namespace RegistryPreviewUILib
|
||||
}
|
||||
catch
|
||||
{
|
||||
saveSuccess = false;
|
||||
|
||||
// this catch handles all other exceptions thrown when trying to write the file out
|
||||
ShowMessageBox(
|
||||
resourceLoader.GetString("ErrorDialogTitle"),
|
||||
@@ -983,6 +1076,8 @@ namespace RegistryPreviewUILib
|
||||
|
||||
// restore the cursor
|
||||
ChangeCursor(gridPreview, false);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1098,4 +1193,4 @@ namespace RegistryPreviewUILib
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -258,12 +258,18 @@
|
||||
<data name="YesNoCancelDialogCloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="ReloadDialogCloseButtonText" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="YesNoCancelDialogContent" xml:space="preserve">
|
||||
<value>Save changes?</value>
|
||||
</data>
|
||||
<data name="YesNoCancelDialogPrimaryButtonText" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="ReloadDialogPrimaryButtonText" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="YesNoCancelDialogSecondaryButtonText" xml:space="preserve">
|
||||
<value>Don't save</value>
|
||||
</data>
|
||||
@@ -300,4 +306,7 @@
|
||||
<data name="NewButton.Label" xml:space="preserve">
|
||||
<value>New</value>
|
||||
</data>
|
||||
<data name="ReloadDialogContent" xml:space="preserve">
|
||||
<value>You lose any unsaved changes. Reload anyway?</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user