mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-27 06:27:25 +01:00
Compare commits
5 Commits
feature/UI
...
leilzh/pdf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48df7911e4 | ||
|
|
bdedc02ea5 | ||
|
|
b2d7182dcd | ||
|
|
82e9d42e02 | ||
|
|
74d92df078 |
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -1716,6 +1716,7 @@ trx
|
||||
tsa
|
||||
TSender
|
||||
TServer
|
||||
tskill
|
||||
tstoi
|
||||
TStr
|
||||
tweakme
|
||||
|
||||
@@ -154,6 +154,9 @@
|
||||
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
|
||||
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\Themes\Themes.vcxproj">
|
||||
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
#include <common/Telemetry/EtwTrace/EtwTrace.h>
|
||||
|
||||
#include <common/Themes/theme_helpers.h>
|
||||
#include <common/Themes/theme_listener.h>
|
||||
|
||||
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
||||
|
||||
namespace winrt
|
||||
@@ -35,6 +38,23 @@ namespace util
|
||||
const std::wstring instanceMutexName = L"Local\\PowerToys_CropAndLock_InstanceMutex";
|
||||
bool m_running = true;
|
||||
|
||||
// Theming
|
||||
ThemeListener theme_listener{};
|
||||
// Keep a list of our cropped windows
|
||||
std::vector<std::shared_ptr<CropAndLockWindow>> croppedWindows;
|
||||
|
||||
void handleTheme()
|
||||
{
|
||||
auto theme = theme_listener.AppTheme;
|
||||
auto isDark = theme == Theme::Dark;
|
||||
Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light");
|
||||
for (auto&& croppedWindow : croppedWindows)
|
||||
{
|
||||
ThemeHelpers::SetImmersiveDarkMode(croppedWindow->Handle(), isDark);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _In_ int)
|
||||
{
|
||||
// Initialize COM
|
||||
@@ -42,6 +62,8 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
|
||||
|
||||
Trace::CropAndLock::RegisterProvider();
|
||||
|
||||
theme_listener.AddChangedHandler(handleTheme);
|
||||
|
||||
Shared::Trace::ETWTrace trace;
|
||||
trace.UpdateState(true);
|
||||
|
||||
@@ -107,8 +129,6 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
|
||||
// Create our overlay window
|
||||
std::unique_ptr<OverlayWindow> overlayWindow;
|
||||
|
||||
// Keep a list of our cropped windows
|
||||
std::vector<std::shared_ptr<CropAndLockWindow>> croppedWindows;
|
||||
|
||||
// Handles and thread for the events sent from runner
|
||||
HANDLE m_reparent_event_handle;
|
||||
@@ -167,6 +187,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
|
||||
croppedWindow->CropAndLock(targetWindow, cropRect);
|
||||
croppedWindow->OnClosed(removeWindowCallback);
|
||||
croppedWindows.push_back(croppedWindow);
|
||||
handleTheme();
|
||||
};
|
||||
|
||||
overlayWindow.reset();
|
||||
|
||||
@@ -110,6 +110,13 @@ internal static class Commands
|
||||
});
|
||||
}
|
||||
|
||||
results.Add(new ListItem(new ExecuteCommandConfirmation(Resources.Microsoft_plugin_sys_RestartShell_name!, confirmCommands, Resources.Microsoft_plugin_sys_RestartShell_confirmation!, static () => OpenInShellHelper.OpenInShell("cmd", "/C tskill explorer && start explorer", runWithHiddenWindow: true)))
|
||||
{
|
||||
Title = Resources.Microsoft_plugin_sys_RestartShell!,
|
||||
Subtitle = Resources.Microsoft_plugin_sys_RestartShell_description!,
|
||||
Icon = Icons.RestartShellIcon,
|
||||
});
|
||||
|
||||
// UEFI command/result. It is only available on systems booted in UEFI mode.
|
||||
if (isUefi)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,8 @@ public static partial class Icons
|
||||
|
||||
public static IconInfo RestartIcon { get; } = new IconInfo("\uE777");
|
||||
|
||||
public static IconInfo RestartShellIcon { get; } = new IconInfo("\uEC50");
|
||||
|
||||
public static IconInfo ShutdownIcon { get; } = new IconInfo("\uE7E8");
|
||||
|
||||
public static IconInfo SleepIcon { get; } = new IconInfo("\uE708");
|
||||
|
||||
@@ -645,6 +645,42 @@ namespace Microsoft.CmdPal.Ext.System {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Restart Windows Explorer.
|
||||
/// </summary>
|
||||
public static string Microsoft_plugin_sys_RestartShell {
|
||||
get {
|
||||
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You are about to restart Windows Explorer, are you sure?.
|
||||
/// </summary>
|
||||
public static string Microsoft_plugin_sys_RestartShell_confirmation {
|
||||
get {
|
||||
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_confirmation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to End and restart the Windows Explorer shell process.
|
||||
/// </summary>
|
||||
public static string Microsoft_plugin_sys_RestartShell_description {
|
||||
get {
|
||||
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Restart.
|
||||
/// </summary>
|
||||
public static string Microsoft_plugin_sys_RestartShell_name {
|
||||
get {
|
||||
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ip; mac; address.
|
||||
/// </summary>
|
||||
|
||||
@@ -417,4 +417,16 @@
|
||||
<data name="Microsoft_plugin_ext_fallback_display_title" xml:space="preserve">
|
||||
<value>Open System Command</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_RestartShell" xml:space="preserve">
|
||||
<value>Restart Windows Explorer</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_RestartShell_description" xml:space="preserve">
|
||||
<value>End and restart the Windows Explorer shell process</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_RestartShell_name" xml:space="preserve">
|
||||
<value>Restart</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_RestartShell_confirmation" xml:space="preserve">
|
||||
<value>You are about to restart Windows Explorer, are you sure?</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -68,6 +68,7 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
|
||||
Title = result.Title;
|
||||
Subtitle = result.Subtitle;
|
||||
Icon = result.Icon;
|
||||
Command = result.Command;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -228,6 +228,8 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf
|
||||
DestinationWidth = (uint)this.ClientSize.Width,
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
stream.Seek(0); // Reset the stream position to the beginning before reading.
|
||||
|
||||
imageOfPage = Image.FromStream(stream.AsStream());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,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>
|
||||
@@ -76,6 +80,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>
|
||||
@@ -106,11 +171,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!
|
||||
@@ -137,14 +206,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +224,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>
|
||||
@@ -161,47 +239,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)
|
||||
@@ -209,10 +264,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,
|
||||
};
|
||||
|
||||
@@ -227,15 +281,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;
|
||||
}
|
||||
}
|
||||
@@ -243,16 +292,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>
|
||||
@@ -313,15 +360,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;
|
||||
}
|
||||
}
|
||||
@@ -411,10 +463,29 @@ 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;
|
||||
}
|
||||
|
||||
// Commands to show data preview
|
||||
public void ButtonExtendedPreview_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
@@ -24,7 +25,10 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
private const string NEWFILEHEADER = "Windows Registry Editor Version 5.00\r\n\r\n";
|
||||
|
||||
private static readonly string _unsavedFileIndicator = "* ";
|
||||
private static readonly char[] _unsavedFileIndicatorChars = [' ', '*'];
|
||||
private static SemaphoreSlim _dialogSemaphore = new(1);
|
||||
|
||||
private string lastKeyPath;
|
||||
|
||||
public delegate void UpdateWindowTitleFunction(string title);
|
||||
@@ -832,42 +836,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>
|
||||
@@ -927,11 +955,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
|
||||
@@ -955,10 +1043,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"),
|
||||
@@ -967,6 +1058,8 @@ namespace RegistryPreviewUILib
|
||||
}
|
||||
catch
|
||||
{
|
||||
saveSuccess = false;
|
||||
|
||||
// this catch handles all other exceptions thrown when trying to write the file out
|
||||
ShowMessageBox(
|
||||
resourceLoader.GetString("ErrorDialogTitle"),
|
||||
@@ -984,6 +1077,8 @@ namespace RegistryPreviewUILib
|
||||
|
||||
// restore the cursor
|
||||
ChangeCursor(gridPreview, false);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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>
|
||||
@@ -363,4 +369,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