This commit is contained in:
rogier committed 2026-11-07 12:19:45 +01:00
commit d55d8bc32d
93 files changed
+13507

No files matched your search

+469
View File
@@ -0,0 +1,469 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Kniip.Capture;
using Kniip.Editor;
using Kniip.Infrastructure;
using Kniip.Upload;
namespace Kniip.Settings;
public partial class SettingsWindow : Window
{
private const string HotkeyHintText =
"kniip vangt deze toetsen in alle programma's af; bij Print Screen opent het Windows-knipprogramma dan niet óók.";
private static readonly Dictionary<string, string> HotkeyNames = new()
{
["Snip"] = "rechthoek knippen",
["Window"] = "venster knippen",
["Record"] = "screencast (MP4)",
["Gif"] = "screencast (GIF)",
};
private readonly AppSettings _settings;
private readonly Dictionary<string, string> _hotkeys;
private readonly Dictionary<string, Button> _hotkeyButtons;
private string? _listeningFor;
private string? _snipFolder;
private string? _videoFolder;
private UploaderConfig? _uploader;
private bool _resetEditor;
public SettingsWindow(AppSettings settings)
{
InitializeComponent();
_settings = settings;
FolderText.Text = CaptureCoordinator.ScreenshotsFolder ?? "Map Schermafbeeldingen niet gevonden.";
AutostartBox.IsChecked = ShellIntegration.IsAutostartEnabled();
OpenWithBox.IsChecked = ShellIntegration.IsOpenWithRegistered();
WatchClipboardBox.Checked += (_, _) => OnlySnippingToolBox.IsEnabled = true;
WatchClipboardBox.Unchecked += (_, _) => OnlySnippingToolBox.IsEnabled = false;
_uploader = settings.Uploader;
ShowUploader();
_hotkeys = new Dictionary<string, string>();
_hotkeyButtons = new Dictionary<string, Button>
{
["Snip"] = SnipHotkeyButton,
["Window"] = WindowHotkeyButton,
["Record"] = RecordHotkeyButton,
["Gif"] = GifHotkeyButton,
};
HotkeyHint.Text = App.Instance.HotkeyHook?.IsActive == true
? HotkeyHintText
: "De toetsenbordhaak kon niet worden gestart; de sneltoetsen werken nu niet.";
Deactivated += (_, _) => StopListening();
Closed += (_, _) => StopListening();
LoadFrom(settings);
MaxHeight = SystemParameters.WorkArea.Height;
}
/// <summary>Vult het formulier; de uploader en de Windows-integratie blijven ongemoeid.</summary>
private void LoadFrom(AppSettings settings)
{
WatchClipboardBox.IsChecked = settings.WatchClipboard;
OnlySnippingToolBox.IsChecked = settings.OnlyFromSnippingTool;
OnlySnippingToolBox.IsEnabled = settings.WatchClipboard;
WatchFolderBox.IsChecked = settings.WatchScreenshotsFolder;
ActionNotifyRadio.IsChecked = settings.Action == CaptureAction.Notify;
ActionOpenRadio.IsChecked = settings.Action == CaptureAction.OpenEditor;
ActionNothingRadio.IsChecked = settings.Action == CaptureAction.Nothing;
StraightHighlightBox.IsChecked = settings.StraightHighlight;
_snipFolder = settings.SnipSaveFolder;
_videoFolder = settings.VideoSaveFolder;
ShowSaveFolders();
SaveAndCopyRadio.IsChecked = settings.SaveButton == SaveButtonChoice.SaveAndCopy;
SaveOnlyRadio.IsChecked = settings.SaveButton != SaveButtonChoice.SaveAndCopy;
_hotkeys["Snip"] = settings.SnipHotkey;
_hotkeys["Window"] = settings.WindowHotkey;
_hotkeys["Record"] = settings.RecordHotkey;
_hotkeys["Gif"] = settings.GifHotkey;
foreach (string id in _hotkeys.Keys)
{
ShowHotkey(id);
}
SystemAudioBox.IsChecked = settings.RecordSystemAudio;
MicrophoneBox.IsChecked = settings.RecordMicrophone;
ShowCursorBox.IsChecked = settings.RecordShowCursor;
HighlightClicksBox.IsChecked = settings.RecordHighlightClicks;
FillChoices(VideoFpsCombo, new[] { 15, 30, 60 }, settings.RecordFps, fps => $"{fps} beelden/s");
FillChoices(GifFpsCombo, new[] { 5, 10, 15, 20, 25 }, settings.GifFps, fps => $"{fps} beelden/s");
FillChoices(GifWidthCombo, new[] { 0, 1920, 1280, 960, 800, 640, 480 }, settings.GifMaxWidth,
width => width == 0 ? "Originele breedte" : $"max. {width} px breed");
}
private void ResetDefaults_Click(object sender, RoutedEventArgs e)
{
StopListening();
LoadFrom(new AppSettings());
_resetEditor = true;
ResetStatus.Visibility = Visibility.Visible;
}
private static void FillChoices(ComboBox combo, int[] values, int current, Func<int, string> label)
{
combo.Items.Clear();
foreach (int value in values.Contains(current) ? values : values.Append(current).ToArray())
{
var item = new ComboBoxItem { Content = label(value), Tag = value };
combo.Items.Add(item);
if (value == current)
{
combo.SelectedItem = item;
}
}
}
private static int SelectedValue(ComboBox combo, int fallback) =>
combo.SelectedItem is ComboBoxItem { Tag: int value } ? value : fallback;
private void ShowHotkey(string id) =>
_hotkeyButtons[id].Content = HotkeyGesture.TryParse(_hotkeys[id], out HotkeyGesture gesture) ? gesture.DisplayText : "(geen)";
private void HotkeyButton_Click(object sender, RoutedEventArgs e)
{
GlobalHotkeyHook? hook = App.Instance.HotkeyHook;
if (hook is not { IsActive: true } || sender is not FrameworkElement { Tag: string id })
{
return;
}
StopListening();
// Via de haak, niet via WPF: Print Screen komt nooit als KeyDown bij een venster aan.
_listeningFor = id;
hook.Capture = OnHotkeyPressed;
_hotkeyButtons[id].Content = "Druk op een toets...";
HotkeyHint.Text = "Esc = annuleren · Delete = geen sneltoets";
}
private void OnHotkeyPressed(HotkeyGesture pressed)
{
if (_listeningFor is not string id)
{
return;
}
if (pressed.Modifiers == ModifierKeys.None && pressed.Key == Key.Escape)
{
StopListening();
return;
}
if (pressed.Modifiers == ModifierKeys.None && pressed.Key is Key.Delete or Key.Back)
{
_hotkeys[id] = string.Empty;
StopListening();
return;
}
if (!pressed.IsAllowed)
{
HotkeyHint.Text = $"{pressed.DisplayText} gebruik je om te typen. Kies Print Screen, Pause, een F-toets of een combinatie met Ctrl, Alt of Win.";
return;
}
foreach ((string other, string hotkey) in _hotkeys)
{
if (other != id && HotkeyGesture.TryParse(hotkey, out HotkeyGesture taken) && taken == pressed)
{
HotkeyHint.Text = $"{pressed.DisplayText} is al de sneltoets voor {HotkeyNames[other]}; kies een andere.";
return;
}
}
_hotkeys[id] = pressed.ToString();
StopListening();
}
private void StopListening()
{
if (_listeningFor is not string id)
{
return;
}
_listeningFor = null;
if (App.Instance.HotkeyHook is { } hook)
{
hook.Capture = null;
}
ShowHotkey(id);
HotkeyHint.Text = HotkeyHintText;
}
private void ClearHotkey_Click(object sender, RoutedEventArgs e)
{
if (sender is not FrameworkElement { Tag: string id })
{
return;
}
StopListening();
_hotkeys[id] = string.Empty;
ShowHotkey(id);
}
private void ShowSaveFolders()
{
ShowSaveFolder(SnipFolderText, _snipFolder);
ShowSaveFolder(VideoFolderText, _videoFolder);
}
private static void ShowSaveFolder(TextBlock text, string? folder)
{
text.Text = string.IsNullOrEmpty(folder) ? "Elke keer vragen" : folder;
text.ToolTip = folder;
text.Foreground = string.IsNullOrEmpty(folder) ? (Brush)Application.Current.Resources["SubtleTextBrush"] : SystemColors.ControlTextBrush;
}
private void BrowseSaveFolder_Click(object sender, RoutedEventArgs e)
{
bool snip = sender is FrameworkElement { Tag: "Snip" };
string? current = snip ? _snipFolder : _videoFolder;
var dialog = new Microsoft.Win32.OpenFolderDialog
{
Title = snip ? "Map voor knipsels kiezen" : "Map voor screencasts kiezen",
InitialDirectory = current is { Length: > 0 } && System.IO.Directory.Exists(current)
? current
: Environment.GetFolderPath(snip ? Environment.SpecialFolder.MyPictures : Environment.SpecialFolder.MyVideos)
};
if (dialog.ShowDialog(this) != true)
{
return;
}
if (snip)
{
_snipFolder = dialog.FolderName;
}
else
{
_videoFolder = dialog.FolderName;
}
ShowSaveFolders();
}
private void ClearSaveFolder_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement { Tag: "Snip" })
{
_snipFolder = null;
}
else
{
_videoFolder = null;
}
ShowSaveFolders();
}
private void ShowUploader()
{
bool configured = _uploader is { IsConfigured: true };
UploaderText.Text = configured ? _uploader!.DisplayName : "Niet ingesteld";
UploaderText.ToolTip = configured ? _uploader!.RequestUrl : null;
UploaderText.Foreground = configured ? SystemColors.ControlTextBrush : (Brush)Application.Current.Resources["SubtleTextBrush"];
TestUploaderButton.IsEnabled = configured;
RemoveUploaderButton.IsEnabled = configured;
}
private void ShowUploaderStatus(string? message, string? url = null, string? suffix = null)
{
UploaderStatus.Inlines.Clear();
UploaderStatus.Inlines.Add(new Run(message ?? string.Empty));
if (url != null)
{
var link = new Hyperlink(new Run(url)) { ToolTip = "Openen in de browser" };
link.Click += (_, _) => OpenUrl(url);
UploaderStatus.Inlines.Add(link);
UploaderStatus.Inlines.Add(new Run(suffix ?? string.Empty));
}
UploaderStatus.Visibility = string.IsNullOrEmpty(message) && url == null ? Visibility.Collapsed : Visibility.Visible;
}
private static void OpenUrl(string url)
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(url) { UseShellExecute = true });
}
catch (Exception)
{
// De link staat ook op het klembord.
}
}
private void ImportUploader_Click(object sender, RoutedEventArgs e)
{
var dialog = new Microsoft.Win32.OpenFileDialog
{
Title = "Zipline-uploader importeren",
Filter = "Zipline-uploader (*.sxcu;*.sh)|*.sxcu;*.sh|ShareX-uploader (*.sxcu)|*.sxcu|Shell script (*.sh)|*.sh|Alle bestanden (*.*)|*.*",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) is { } home
&& System.IO.Directory.Exists(System.IO.Path.Combine(home, "Downloads"))
? System.IO.Path.Combine(home, "Downloads")
: string.Empty
};
if (dialog.ShowDialog(this) != true)
{
return;
}
try
{
_uploader = UploaderImporter.FromFile(dialog.FileName);
ShowUploader();
ShowUploaderStatus($"Geïmporteerd: {_uploader.Name}. Klik op Opslaan om dit te bewaren.");
}
catch (Exception ex)
{
MessageBox.Show(this, $"Dit bestand kan niet worden gebruikt:\n{dialog.FileName}\n\n{ex.Message}", "kniip",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
private async void TestUploader_Click(object sender, RoutedEventArgs e)
{
if (_uploader is not { IsConfigured: true } config)
{
return;
}
TestUploaderButton.IsEnabled = false;
ShowUploaderStatus("Testplaatje uploaden...");
try
{
using System.IO.MemoryStream png = ImageExporter.EncodePng(CreateTestImage());
string url = await ZiplineUploader.UploadAsync(config, png, "kniip-test.png", "image/png");
bool copied = ClipboardWriter.SetText(url);
ShowUploaderStatus("Gelukt: ", url, copied ? " (gekopieerd)" : null);
}
catch (Exception ex)
{
ShowUploaderStatus(null);
MessageBox.Show(this, $"De test-upload is mislukt.\n\n{ex.Message}", "kniip", MessageBoxButton.OK, MessageBoxImage.Warning);
}
finally
{
TestUploaderButton.IsEnabled = _uploader is { IsConfigured: true };
}
}
private static BitmapSource CreateTestImage()
{
const int size = 64;
Color accent = Application.Current.Resources["AccentBrush"] is SolidColorBrush brush ? brush.Color : Colors.SteelBlue;
var pixels = new byte[size * size * 4];
for (int i = 0; i < pixels.Length; i += 4)
{
pixels[i] = accent.B;
pixels[i + 1] = accent.G;
pixels[i + 2] = accent.R;
pixels[i + 3] = 255;
}
return BitmapSource.Create(size, size, 96, 96, PixelFormats.Bgra32, null, pixels, size * 4);
}
private void RemoveUploader_Click(object sender, RoutedEventArgs e)
{
_uploader = null;
ShowUploader();
ShowUploaderStatus(null);
}
private void Save_Click(object sender, RoutedEventArgs e)
{
_settings.Uploader = _uploader;
_settings.SnipSaveFolder = _snipFolder;
_settings.VideoSaveFolder = _videoFolder;
_settings.SaveButton = SaveAndCopyRadio.IsChecked == true
? SaveButtonChoice.SaveAndCopy
: SaveButtonChoice.SaveOnly;
_settings.WatchClipboard = WatchClipboardBox.IsChecked == true;
_settings.OnlyFromSnippingTool = OnlySnippingToolBox.IsChecked == true;
_settings.WatchScreenshotsFolder = WatchFolderBox.IsChecked == true;
_settings.Action = ActionOpenRadio.IsChecked == true ? CaptureAction.OpenEditor
: ActionNothingRadio.IsChecked == true ? CaptureAction.Nothing
: CaptureAction.Notify;
_settings.StraightHighlight = StraightHighlightBox.IsChecked == true;
StopListening();
_settings.SnipHotkey = _hotkeys["Snip"];
_settings.WindowHotkey = _hotkeys["Window"];
_settings.RecordHotkey = _hotkeys["Record"];
_settings.GifHotkey = _hotkeys["Gif"];
_settings.RecordSystemAudio = SystemAudioBox.IsChecked == true;
_settings.RecordMicrophone = MicrophoneBox.IsChecked == true;
_settings.RecordShowCursor = ShowCursorBox.IsChecked == true;
_settings.RecordHighlightClicks = HighlightClicksBox.IsChecked == true;
_settings.RecordFps = SelectedValue(VideoFpsCombo, 30);
_settings.GifFps = SelectedValue(GifFpsCombo, 10);
_settings.GifMaxWidth = SelectedValue(GifWidthCombo, 800);
if (_resetEditor)
{
var defaults = new AppSettings();
_settings.ToolStyles = defaults.ToolStyles;
_settings.LastTool = defaults.LastTool;
_settings.LastSaveFolder = defaults.LastSaveFolder;
_settings.LastVideoFolder = defaults.LastVideoFolder;
_settings.ToolbarPosition = defaults.ToolbarPosition;
_settings.ToolbarLocked = defaults.ToolbarLocked;
_settings.ToolbarOrder = defaults.ToolbarOrder;
_settings.ToolbarHidden = defaults.ToolbarHidden;
}
try
{
ShellIntegration.SetAutostart(AutostartBox.IsChecked == true);
bool openWith = OpenWithBox.IsChecked == true;
if (openWith && !ShellIntegration.IsOpenWithRegistered())
{
ShellIntegration.RegisterOpenWith();
}
else if (!openWith && ShellIntegration.IsOpenWithRegistered())
{
ShellIntegration.UnregisterOpenWith();
}
}
catch (Exception ex)
{
MessageBox.Show(this, $"Windows-integratie kon niet worden bijgewerkt:\n\n{ex.Message}", "kniip",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
App.Instance.SaveSettings();
Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e) => Close();
private void Tabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// ComboBox-keuzes bubbelen dezelfde gebeurtenis; alleen een echt tabblad telt.
if (e.OriginalSource is TabControl)
{
StopListening();
}
}
}