1.1.4
Update-melding, rechtermuisknopmenu in de editor. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
67c98c7c0a
commit
b780c6a116
12 files changed
+405
-24
No files matched your search
@@ -6,6 +6,13 @@ Alle noemenswaardige wijzigingen in kniip staan in dit bestand. De nieuwste vers
|
|||||||
|
|
||||||
### Nieuw
|
### Nieuw
|
||||||
|
|
||||||
|
- **Melding bij een nieuwe versie.** kniip kijkt bij het starten en daarna elke dag of er een nieuwere versie is. Is die er, dan staat er een blauwe stip op de knop **Instellingen** in de editor en op de tab **Update** in de instellingen. Wil je dat niet, zet dan in de tab **Update** het vinkje **Automatisch controleren op nieuwe versies** uit.
|
||||||
|
- **Uitgebreid rechtermuisknopmenu in de editor.** Klik met rechts op het canvas, ook op een leeg stuk, voor een menu met iconen en sneltoetsen. Wat op dat moment niet kan, staat grijs.
|
||||||
|
- **Ongedaan maken** en **Opnieuw**.
|
||||||
|
- **Verwijder**, **Verwijder alles** (Shift+Delete, de afbeelding blijft staan) en **Samenvoegen** (Ctrl+Shift+F). Samenvoegen maakt de annotaties deel van de afbeelding en kun je ongedaan maken.
|
||||||
|
- **Knip** (Ctrl+X), **Kopieer** (Ctrl+Shift+C), **Plak** (Ctrl+V) en **Dupliceren** (Ctrl+D). Plak via het menu zet de annotatie op de plek waar je rechts klikte. Kopiëren en plakken werkt ook tussen twee editorvensters.
|
||||||
|
- **Naar voorgrond** (Home), **Naar voren** (PageUp), **Naar achteren** (PageDown) en **Naar achtergrond** (End).
|
||||||
|
- **Werkbalken verbergen** (Ctrl+H). Via hetzelfde menu of Ctrl+H haal je ze terug. Bij een nieuwe start zijn ze er altijd weer.
|
||||||
- **Lijnen en pijlen buigen.** Selecteer een lijn of pijl. Midden op elk stuk staat dan een klein rondje. Sleep dat rondje en het wordt een buigpunt. De lijn loopt daarna als vloeiende kromme door alle punten. Zo kun je zoveel buigpunten toevoegen als je wilt.
|
- **Lijnen en pijlen buigen.** Selecteer een lijn of pijl. Midden op elk stuk staat dan een klein rondje. Sleep dat rondje en het wordt een buigpunt. De lijn loopt daarna als vloeiende kromme door alle punten. Zo kun je zoveel buigpunten toevoegen als je wilt.
|
||||||
- Dubbelklik op een buigpunt om het weer te verwijderen.
|
- Dubbelklik op een buigpunt om het weer te verwijderen.
|
||||||
- Bij een pijl draait de punt mee met de richting van het laatste stuk.
|
- Bij een pijl draait de punt mee met de richting van het laatste stuk.
|
||||||
@@ -14,6 +21,9 @@ Alle noemenswaardige wijzigingen in kniip staan in dit bestand. De nieuwste vers
|
|||||||
|
|
||||||
### Gewijzigd
|
### Gewijzigd
|
||||||
|
|
||||||
|
- kniip zoekt nu standaard zelf op de achtergrond naar updates. Tot nu toe gebeurde dat alleen als je de tab **Update** opende of op **Opnieuw controleren** klikte. Een mislukte controle, bijvoorbeeld zonder internet, geeft geen foutmelding.
|
||||||
|
- **Ctrl+V** plakt nu eerst een gekopieerde annotatie. Heb je daarna iets anders gekopieerd, dan opent Ctrl+V zoals voorheen de afbeelding van het klembord.
|
||||||
|
- Het rechtermuisknopmenu van de werkbalk heeft dezelfde nieuwe stijl gekregen.
|
||||||
- **Testversie naast kniip.** `build.ps1` maakt nu ook `KniipTest-<versie>.exe`. Die testversie heeft eigen instellingen en registervermeldingen, zodat hij naast de geïnstalleerde kniip kan draaien zonder die te raken. De testversie werkt zichzelf niet bij. Met `-SkipTest` sla je hem over.
|
- **Testversie naast kniip.** `build.ps1` maakt nu ook `KniipTest-<versie>.exe`. Die testversie heeft eigen instellingen en registervermeldingen, zodat hij naast de geïnstalleerde kniip kan draaien zonder die te raken. De testversie werkt zichzelf niet bij. Met `-SkipTest` sla je hem over.
|
||||||
|
|
||||||
## 1.1.3 - 2026-11-09
|
## 1.1.3 - 2026-11-09
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ Draaien beide versies tegelijk, dan reageren ze allebei op dezelfde sneltoetsen
|
|||||||
|
|
||||||
Onder **Instellingen > Update** controleert kniip via de API van git.hibbit.net of er een nieuwere release is en toont de changelog van alle nieuwere versies. **Updaten** downloadt de installer, sluit kniip, installeert stil en start kniip opnieuw. De eerder gekozen integraties blijven behouden.
|
Onder **Instellingen > Update** controleert kniip via de API van git.hibbit.net of er een nieuwere release is en toont de changelog van alle nieuwere versies. **Updaten** downloadt de installer, sluit kniip, installeert stil en start kniip opnieuw. De eerder gekozen integraties blijven behouden.
|
||||||
|
|
||||||
|
kniip controleert ook zelf bij het starten en daarna elke 24 uur. Is er een nieuwere versie, dan staat er een blauwe stip op de knop **Instellingen** in de editor en op de tab **Update**. Dit zet je uit met **Automatisch controleren op nieuwe versies** in die tab.
|
||||||
|
|
||||||
Voorwaarden:
|
Voorwaarden:
|
||||||
|
|
||||||
- De repository `rogier/kniip` is publiek; kniip gebruikt geen token.
|
- De repository `rogier/kniip` is publiek; kniip gebruikt geen token.
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public partial class App : Application
|
|||||||
/// <summary>Toetsenbordhaak voor alle sneltoetsen (knipsels en screencasts).</summary>
|
/// <summary>Toetsenbordhaak voor alle sneltoetsen (knipsels en screencasts).</summary>
|
||||||
internal GlobalHotkeyHook? HotkeyHook { get; private set; }
|
internal GlobalHotkeyHook? HotkeyHook { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Achtergrondcontrole op nieuwe versies; stuurt de blauwe stip bij Instellingen.</summary>
|
||||||
|
public UpdateNotifier Updates { get; } = new();
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
@@ -105,6 +108,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
HotkeyHook = new GlobalHotkeyHook(Dispatcher);
|
HotkeyHook = new GlobalHotkeyHook(Dispatcher);
|
||||||
ApplyHotkeys();
|
ApplyHotkeys();
|
||||||
|
Updates.Apply(Settings.CheckForUpdates);
|
||||||
|
|
||||||
_singleInstance.StartServer(received => Dispatcher.BeginInvoke(() => HandleArguments(received, false)));
|
_singleInstance.StartServer(received => Dispatcher.BeginInvoke(() => HandleArguments(received, false)));
|
||||||
HandleArguments(args, true);
|
HandleArguments(args, true);
|
||||||
@@ -440,6 +444,7 @@ public partial class App : Application
|
|||||||
SettingsStore.Save(Settings);
|
SettingsStore.Save(Settings);
|
||||||
Capture.ApplySettings(Settings);
|
Capture.ApplySettings(Settings);
|
||||||
ApplyHotkeys();
|
ApplyHotkeys();
|
||||||
|
Updates.Apply(Settings.CheckForUpdates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BringToFront(Window window)
|
private static void BringToFront(Window window)
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
using Kniip.Editor.Annotations;
|
||||||
|
|
||||||
|
namespace Kniip.Editor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gekopieerde annotatie, gedeeld tussen editorvensters. Op het Windows-klembord staat alleen een markering,
|
||||||
|
/// zodat iets wat daarna in een ander programma wordt gekopieerd bij plakken voorgaat.
|
||||||
|
/// </summary>
|
||||||
|
internal static class AnnotationClipboard
|
||||||
|
{
|
||||||
|
private const string MarkerFormat = "Kniip.Annotation";
|
||||||
|
|
||||||
|
private static Annotation? _item;
|
||||||
|
private static string? _token;
|
||||||
|
|
||||||
|
public static bool Set(Annotation item)
|
||||||
|
{
|
||||||
|
string token = Guid.NewGuid().ToString("N");
|
||||||
|
if (!ClipboardWriter.SetMarker(MarkerFormat, token))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_item = item.Clone();
|
||||||
|
_token = token;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Een kopie van de annotatie, of null als het klembord inmiddels iets anders bevat.</summary>
|
||||||
|
public static Annotation? Get()
|
||||||
|
{
|
||||||
|
if (_item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Clipboard.GetData(MarkerFormat) as string == _token ? _item.Clone() : null;
|
||||||
|
}
|
||||||
|
catch (ExternalException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,13 @@ public sealed class EditorDocument
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Vervangt het knipsel door de platgemaakte versie; de annotaties zitten daarna in de pixels.</summary>
|
||||||
|
public void Flatten(BitmapSource rendered)
|
||||||
|
{
|
||||||
|
SetPixels(PixelUtil.ToOpaqueBgra(rendered), rendered.PixelWidth, rendered.PixelHeight);
|
||||||
|
Items = new List<Annotation>();
|
||||||
|
}
|
||||||
|
|
||||||
public string GetStepLabel(StepAnnotation step)
|
public string GetStepLabel(StepAnnotation step)
|
||||||
{
|
{
|
||||||
int ordinal = 0;
|
int ordinal = 0;
|
||||||
|
|||||||
@@ -315,9 +315,14 @@
|
|||||||
<TextBlock Text="" Style="{StaticResource IconText}" />
|
<TextBlock Text="" Style="{StaticResource IconText}" />
|
||||||
</Button>
|
</Button>
|
||||||
<local:ToolbarSeparator local:ToolbarPanel.ItemId="Sep5" />
|
<local:ToolbarSeparator local:ToolbarPanel.ItemId="Sep5" />
|
||||||
<Button local:ToolbarPanel.ItemId="Settings" local:ToolbarPanel.Title="Instellingen"
|
<Button x:Name="SettingsButton" local:ToolbarPanel.ItemId="Settings" local:ToolbarPanel.Title="Instellingen"
|
||||||
Style="{StaticResource FlatButton}" ToolTip="Instellingen" Click="Settings_Click">
|
Style="{StaticResource FlatButton}" ToolTip="Instellingen" Click="Settings_Click">
|
||||||
<TextBlock Text="" Style="{StaticResource IconText}" />
|
<Grid>
|
||||||
|
<TextBlock Text="" Style="{StaticResource IconText}" />
|
||||||
|
<Ellipse x:Name="SettingsBadge" Width="8" Height="8" Fill="{StaticResource AccentBrush}" Stroke="White"
|
||||||
|
StrokeThickness="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-2,-3,0"
|
||||||
|
IsHitTestVisible="False" Visibility="Collapsed" />
|
||||||
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="SaveButton" local:ToolbarPanel.ItemId="Save" local:ToolbarPanel.Title="Opslaan"
|
<Button x:Name="SaveButton" local:ToolbarPanel.ItemId="Save" local:ToolbarPanel.Title="Opslaan"
|
||||||
Style="{StaticResource SecondaryButton}" ToolTip="Opslaan (Ctrl+S); Shift+klik of Ctrl+Shift+S = opslaan als" Click="Save_Click">
|
Style="{StaticResource SecondaryButton}" ToolTip="Opslaan (Ctrl+S); Shift+klik of Ctrl+Shift+S = opslaan als" Click="Save_Click">
|
||||||
@@ -344,7 +349,7 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Eigenschappen van het actieve gereedschap of het geselecteerde object -->
|
<!-- Eigenschappen van het actieve gereedschap of het geselecteerde object -->
|
||||||
<Border DockPanel.Dock="Top" Background="White" BorderBrush="{StaticResource DividerBrush}" BorderThickness="0,0,0,1"
|
<Border x:Name="PropsBar" DockPanel.Dock="Top" Background="White" BorderBrush="{StaticResource DividerBrush}" BorderThickness="0,0,0,1"
|
||||||
Padding="12,4" MinHeight="42">
|
Padding="12,4" MinHeight="42">
|
||||||
<WrapPanel x:Name="PropsPanel" VerticalAlignment="Center" HorizontalAlignment="Center">
|
<WrapPanel x:Name="PropsPanel" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
<StackPanel x:Name="StrokeGroup" Style="{StaticResource PropGroup}">
|
<StackPanel x:Name="StrokeGroup" Style="{StaticResource PropGroup}">
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public partial class EditorWindow : Window
|
|||||||
{
|
{
|
||||||
private enum DragMode { None, Creating, Moving, Handle, Cropping }
|
private enum DragMode { None, Creating, Moving, Handle, Cropping }
|
||||||
|
|
||||||
|
private enum ZOrder { Front, Forward, Backward, Back }
|
||||||
|
|
||||||
private static readonly double[] ThicknessValues = { 0, 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32 };
|
private static readonly double[] ThicknessValues = { 0, 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32 };
|
||||||
private static readonly double[] FontSizeValues = { 10, 12, 14, 16, 18, 20, 24, 28, 32, 40, 48, 64 };
|
private static readonly double[] FontSizeValues = { 10, 12, 14, 16, 18, 20, 24, 28, 32, 40, 48, 64 };
|
||||||
private static readonly double[] SizeValues = { 16, 20, 24, 28, 32, 40, 48, 56, 64, 80 };
|
private static readonly double[] SizeValues = { 16, 20, 24, 28, 32, 40, 48, 56, 64, 80 };
|
||||||
@@ -68,6 +70,7 @@ public partial class EditorWindow : Window
|
|||||||
private bool _dirty;
|
private bool _dirty;
|
||||||
private bool _discarding;
|
private bool _discarding;
|
||||||
private bool _exported;
|
private bool _exported;
|
||||||
|
private bool _toolbarsHidden;
|
||||||
private readonly string? _windowName;
|
private readonly string? _windowName;
|
||||||
|
|
||||||
/// <param name="windowName">Programmanaam van het geknipte venster, voor %V in het naamsjabloon.</param>
|
/// <param name="windowName">Programmanaam van het geknipte venster, voor %V in het naamsjabloon.</param>
|
||||||
@@ -117,6 +120,10 @@ public partial class EditorWindow : Window
|
|||||||
DpiChanged += (_, _) => ApplyZoom();
|
DpiChanged += (_, _) => ApplyZoom();
|
||||||
PreviewKeyDown += OnPreviewKeyDown;
|
PreviewKeyDown += OnPreviewKeyDown;
|
||||||
|
|
||||||
|
App.Instance.Updates.AvailabilityChanged += OnUpdateAvailabilityChanged;
|
||||||
|
Closed += (_, _) => App.Instance.Updates.AvailabilityChanged -= OnUpdateAvailabilityChanged;
|
||||||
|
ShowUpdateBadge();
|
||||||
|
|
||||||
InitializeToolbar();
|
InitializeToolbar();
|
||||||
ApplySaveButton();
|
ApplySaveButton();
|
||||||
_suppressStyleEvents = false;
|
_suppressStyleEvents = false;
|
||||||
@@ -731,26 +738,53 @@ public partial class EditorWindow : Window
|
|||||||
private void Surface_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
private void Surface_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
CommitTextEdit();
|
CommitTextEdit();
|
||||||
Annotation? hit = HitTest(e.GetPosition(Surface));
|
Point point = e.GetPosition(Surface);
|
||||||
if (hit == null)
|
Annotation? hit = HitTest(point);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetSelected(hit);
|
SetSelected(hit);
|
||||||
|
bool has = hit != null;
|
||||||
|
int index = hit != null ? _doc.Items.IndexOf(hit) : -1;
|
||||||
|
int last = _doc.Items.Count - 1;
|
||||||
|
|
||||||
var menu = new ContextMenu { PlacementTarget = Surface };
|
var menu = new ContextMenu { PlacementTarget = Surface };
|
||||||
menu.Items.Add(MenuItem("Naar voren", () => Reorder(hit, toFront: true)));
|
menu.Items.Add(MenuItem("Ongedaan maken", "\uE7A7", "Ctrl+Z", Undo, _undo.CanUndo));
|
||||||
menu.Items.Add(MenuItem("Naar achteren", () => Reorder(hit, toFront: false)));
|
menu.Items.Add(MenuItem("Opnieuw", "\uE7A6", "Ctrl+Y", Redo, _undo.CanRedo));
|
||||||
menu.Items.Add(MenuItem("Dupliceren", () => Duplicate(hit)));
|
|
||||||
menu.Items.Add(new Separator());
|
menu.Items.Add(new Separator());
|
||||||
menu.Items.Add(MenuItem("Verwijderen", DeleteSelected));
|
menu.Items.Add(MenuItem("Verwijder", "\uE74D", "Delete", DeleteSelected, has));
|
||||||
|
menu.Items.Add(MenuItem("Verwijder alles", "\uE894", "Shift+Delete", DeleteAll, last >= 0));
|
||||||
|
menu.Items.Add(MenuItem("Samenvoegen", "\uE81E", "Ctrl+Shift+F", Flatten, last >= 0));
|
||||||
|
menu.Items.Add(new Separator());
|
||||||
|
menu.Items.Add(MenuItem("Knip", "\uE8C6", "Ctrl+X", () => CutAnnotation(hit!), has));
|
||||||
|
menu.Items.Add(MenuItem("Kopieer", "\uE8C8", "Ctrl+Shift+C", () => CopyAnnotation(hit!), has));
|
||||||
|
menu.Items.Add(MenuItem("Plak", "\uE77F", "Ctrl+V", () => Paste(point), CanPaste()));
|
||||||
|
menu.Items.Add(MenuItem("Dupliceren", "\uE710", "Ctrl+D", () => Duplicate(hit!), has));
|
||||||
|
menu.Items.Add(new Separator());
|
||||||
|
menu.Items.Add(MenuItem("Naar voorgrond", "\uE898", "Home", () => Reorder(hit!, ZOrder.Front), has && index < last));
|
||||||
|
menu.Items.Add(MenuItem("Naar voren", "\uE74A", "PageUp", () => Reorder(hit!, ZOrder.Forward), has && index < last));
|
||||||
|
menu.Items.Add(MenuItem("Naar achteren", "\uE74B", "PageDown", () => Reorder(hit!, ZOrder.Backward), has && index > 0));
|
||||||
|
menu.Items.Add(MenuItem("Naar achtergrond", "\uE896", "End", () => Reorder(hit!, ZOrder.Back), has && index > 0));
|
||||||
|
menu.Items.Add(new Separator());
|
||||||
|
menu.Items.Add(_toolbarsHidden
|
||||||
|
? MenuItem("Werkbalken tonen", "\uE7B3", "Ctrl+H", ToggleToolbars)
|
||||||
|
: MenuItem("Werkbalken verbergen", "\uED1A", "Ctrl+H", ToggleToolbars));
|
||||||
menu.IsOpen = true;
|
menu.IsOpen = true;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MenuItem MenuItem(string header, Action action)
|
private static MenuItem MenuItem(string header, string glyph, string gesture, Action action, bool isEnabled = true)
|
||||||
{
|
{
|
||||||
var item = new MenuItem { Header = header };
|
var item = new MenuItem
|
||||||
|
{
|
||||||
|
Header = header,
|
||||||
|
InputGestureText = gesture,
|
||||||
|
IsEnabled = isEnabled,
|
||||||
|
Icon = new TextBlock
|
||||||
|
{
|
||||||
|
Text = glyph,
|
||||||
|
FontFamily = (FontFamily)Application.Current.FindResource("IconFont"),
|
||||||
|
FontSize = 14,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
}
|
||||||
|
};
|
||||||
item.Click += (_, _) => action();
|
item.Click += (_, _) => action();
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -789,20 +823,121 @@ public partial class EditorWindow : Window
|
|||||||
SetSelected(null);
|
SetSelected(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reorder(Annotation item, bool toFront)
|
private void DeleteAll()
|
||||||
{
|
{
|
||||||
Commit(_doc.CaptureState());
|
if (_doc.Items.Count == 0)
|
||||||
_doc.Items.Remove(item);
|
|
||||||
if (toFront)
|
|
||||||
{
|
{
|
||||||
_doc.Items.Add(item);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Commit(_doc.CaptureState());
|
||||||
|
_doc.Items.Clear();
|
||||||
|
SetSelected(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Flatten()
|
||||||
|
{
|
||||||
|
CommitTextEdit();
|
||||||
|
if (_doc.Items.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Commit(_doc.CaptureState());
|
||||||
|
_doc.Flatten(DocumentRenderer.Export(_doc));
|
||||||
|
SetSelected(null);
|
||||||
|
ShowTemporaryHint("Annotaties samengevoegd met de afbeelding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reorder(Annotation item, ZOrder order)
|
||||||
|
{
|
||||||
|
int index = _doc.Items.IndexOf(item);
|
||||||
|
int last = _doc.Items.Count - 1;
|
||||||
|
int target = order switch
|
||||||
|
{
|
||||||
|
ZOrder.Front => last,
|
||||||
|
ZOrder.Forward => Math.Min(index + 1, last),
|
||||||
|
ZOrder.Backward => Math.Max(index - 1, 0),
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
if (index < 0 || target == index)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Commit(_doc.CaptureState());
|
||||||
|
_doc.Items.RemoveAt(index);
|
||||||
|
_doc.Items.Insert(target, item);
|
||||||
|
Surface.InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyAnnotation(Annotation item)
|
||||||
|
{
|
||||||
|
if (!AnnotationClipboard.Set(item))
|
||||||
|
{
|
||||||
|
ShowTemporaryHint("Kopiëren mislukt: het klembord is bezet. Probeer het opnieuw.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CutAnnotation(Annotation item)
|
||||||
|
{
|
||||||
|
if (AnnotationClipboard.Set(item))
|
||||||
|
{
|
||||||
|
SetSelected(item);
|
||||||
|
DeleteSelected();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_doc.Items.Insert(0, item);
|
ShowTemporaryHint("Knippen mislukt: het klembord is bezet. Probeer het opnieuw.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="at">Middelpunt voor de geplakte annotatie; zonder punt een kleine verschuiving.</param>
|
||||||
|
private void Paste(Point? at = null)
|
||||||
|
{
|
||||||
|
if (AnnotationClipboard.Get() is not { } copy)
|
||||||
|
{
|
||||||
|
PasteImage();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface.InvalidateVisual();
|
Commit(_doc.CaptureState());
|
||||||
|
Rect bounds = copy.Bounds;
|
||||||
|
Vector offset = at is { } point
|
||||||
|
? point - new Point(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2)
|
||||||
|
: new Vector(12 / Surface.ViewScale, 12 / Surface.ViewScale);
|
||||||
|
copy.Move(offset);
|
||||||
|
_doc.Items.Add(copy);
|
||||||
|
SetSelected(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanPaste()
|
||||||
|
{
|
||||||
|
if (AnnotationClipboard.Get() != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Clipboard.ContainsImage() || Clipboard.ContainsData("PNG") || Clipboard.ContainsFileDropList();
|
||||||
|
}
|
||||||
|
catch (System.Runtime.InteropServices.ExternalException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleToolbars()
|
||||||
|
{
|
||||||
|
_toolbarsHidden = !_toolbarsHidden;
|
||||||
|
Visibility visibility = _toolbarsHidden ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
Toolbar.Visibility = visibility;
|
||||||
|
PropsBar.Visibility = visibility;
|
||||||
|
if (_toolbarsHidden)
|
||||||
|
{
|
||||||
|
ShowTemporaryHint("Werkbalken verborgen. Ctrl+H of rechtsklik op het canvas om ze terug te halen.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Duplicate(Annotation item)
|
private void Duplicate(Annotation item)
|
||||||
@@ -1547,6 +1682,15 @@ public partial class EditorWindow : Window
|
|||||||
|
|
||||||
private void Settings_Click(object sender, RoutedEventArgs e) => App.Instance.ShowSettings();
|
private void Settings_Click(object sender, RoutedEventArgs e) => App.Instance.ShowSettings();
|
||||||
|
|
||||||
|
private void OnUpdateAvailabilityChanged(object? sender, EventArgs e) => ShowUpdateBadge();
|
||||||
|
|
||||||
|
private void ShowUpdateBadge()
|
||||||
|
{
|
||||||
|
bool available = App.Instance.Updates.IsUpdateAvailable;
|
||||||
|
SettingsBadge.Visibility = available ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
SettingsButton.ToolTip = available ? "Instellingen - nieuwe versie beschikbaar" : "Instellingen";
|
||||||
|
}
|
||||||
|
|
||||||
private void NewSnip_Click(object sender, RoutedEventArgs e) => DiscardAndStartNewSnip();
|
private void NewSnip_Click(object sender, RoutedEventArgs e) => DiscardAndStartNewSnip();
|
||||||
|
|
||||||
private void Clear_Click(object sender, RoutedEventArgs e) => DiscardAndStartBlank();
|
private void Clear_Click(object sender, RoutedEventArgs e) => DiscardAndStartBlank();
|
||||||
@@ -1736,10 +1880,26 @@ public partial class EditorWindow : Window
|
|||||||
break;
|
break;
|
||||||
case Key.Z:
|
case Key.Z:
|
||||||
Undo();
|
Undo();
|
||||||
|
break;
|
||||||
|
case Key.C when shift:
|
||||||
|
if (_selected != null)
|
||||||
|
{
|
||||||
|
CopyAnnotation(_selected);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case Key.C:
|
case Key.C:
|
||||||
CopyToClipboard();
|
CopyToClipboard();
|
||||||
break;
|
break;
|
||||||
|
case Key.X when _selected != null:
|
||||||
|
CutAnnotation(_selected);
|
||||||
|
break;
|
||||||
|
case Key.F when shift:
|
||||||
|
Flatten();
|
||||||
|
break;
|
||||||
|
case Key.H:
|
||||||
|
ToggleToolbars();
|
||||||
|
break;
|
||||||
case Key.S when shift:
|
case Key.S when shift:
|
||||||
SaveAs();
|
SaveAs();
|
||||||
break;
|
break;
|
||||||
@@ -1756,7 +1916,7 @@ public partial class EditorWindow : Window
|
|||||||
DiscardAndStartBlank();
|
DiscardAndStartBlank();
|
||||||
break;
|
break;
|
||||||
case Key.V:
|
case Key.V:
|
||||||
PasteImage();
|
Paste();
|
||||||
break;
|
break;
|
||||||
case Key.O:
|
case Key.O:
|
||||||
OpenImageFile();
|
OpenImageFile();
|
||||||
@@ -1796,10 +1956,25 @@ public partial class EditorWindow : Window
|
|||||||
double step = shift ? 10 : 1;
|
double step = shift ? 10 : 1;
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
|
case Key.Delete when shift:
|
||||||
|
DeleteAll();
|
||||||
|
break;
|
||||||
case Key.Delete:
|
case Key.Delete:
|
||||||
case Key.Back:
|
case Key.Back:
|
||||||
DeleteSelected();
|
DeleteSelected();
|
||||||
break;
|
break;
|
||||||
|
case Key.Home when _selected != null:
|
||||||
|
Reorder(_selected, ZOrder.Front);
|
||||||
|
break;
|
||||||
|
case Key.PageUp when _selected != null:
|
||||||
|
Reorder(_selected, ZOrder.Forward);
|
||||||
|
break;
|
||||||
|
case Key.PageDown when _selected != null:
|
||||||
|
Reorder(_selected, ZOrder.Backward);
|
||||||
|
break;
|
||||||
|
case Key.End when _selected != null:
|
||||||
|
Reorder(_selected, ZOrder.Back);
|
||||||
|
break;
|
||||||
case Key.Escape:
|
case Key.Escape:
|
||||||
if (_drag != DragMode.None)
|
if (_drag != DragMode.None)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ internal static class ClipboardWriter
|
|||||||
return TrySet(data);
|
return TrySet(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Alleen een eigen markering op het klembord, zonder afbeelding of tekst.</summary>
|
||||||
|
public static bool SetMarker(string format, string value)
|
||||||
|
{
|
||||||
|
var data = new DataObject();
|
||||||
|
data.SetData(format, value);
|
||||||
|
return TrySet(data);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tekst gaat rechtstreeks via Win32 op het klembord. De OLE-route van WPF moet het klembord
|
/// Tekst gaat rechtstreeks via Win32 op het klembord. De OLE-route van WPF moet het klembord
|
||||||
/// daarna nog leegspoelen, en dat mislukt zodra een klembordbeheerder het meteen opent.
|
/// daarna nog leegspoelen, en dat mislukt zodra een klembordbeheerder het meteen opent.
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ public sealed class AppSettings
|
|||||||
/// <summary>Zipline-server waar Uploaden naartoe gaat; leeg betekent geen uploadknop.</summary>
|
/// <summary>Zipline-server waar Uploaden naartoe gaat; leeg betekent geen uploadknop.</summary>
|
||||||
public UploaderConfig? Uploader { get; set; }
|
public UploaderConfig? Uploader { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Bij het starten en daarna dagelijks op de achtergrond kijken of er een nieuwere versie is.</summary>
|
||||||
|
public bool CheckForUpdates { get; set; } = true;
|
||||||
|
|
||||||
public StyleSet StyleFor(ToolKind tool)
|
public StyleSet StyleFor(ToolKind tool)
|
||||||
{
|
{
|
||||||
if (!ToolStyles.TryGetValue(tool, out StyleSet? style))
|
if (!ToolStyles.TryGetValue(tool, out StyleSet? style))
|
||||||
|
|||||||
@@ -256,10 +256,19 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem x:Name="UpdateTab" Header="Update">
|
<TabItem x:Name="UpdateTab">
|
||||||
|
<TabItem.Header>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="Update" />
|
||||||
|
<Ellipse x:Name="UpdateTabBadge" Width="8" Height="8" Fill="{StaticResource AccentBrush}" Stroke="White"
|
||||||
|
StrokeThickness="1" Margin="6,0,0,0" VerticalAlignment="Center" Visibility="Collapsed"
|
||||||
|
ToolTip="Er is een nieuwe versie beschikbaar" />
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem.Header>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Update" Style="{StaticResource CardTitle}" />
|
<TextBlock Text="Update" Style="{StaticResource CardTitle}" />
|
||||||
<TextBlock x:Name="CurrentVersionText" Margin="0,0,0,2" />
|
<TextBlock x:Name="CurrentVersionText" Margin="0,0,0,2" />
|
||||||
|
<CheckBox x:Name="AutoUpdateCheckBox" Content="Automatisch controleren op nieuwe versies (bij starten en dagelijks)" />
|
||||||
<TextBlock x:Name="UpdateStatus" Style="{StaticResource Hint}" Margin="0,0,0,8" />
|
<TextBlock x:Name="UpdateStatus" Style="{StaticResource Hint}" Margin="0,0,0,8" />
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||||
<Button x:Name="UpdateButton" Style="{StaticResource AccentButton}" Click="Update_Click" Margin="0,0,6,0"
|
<Button x:Name="UpdateButton" Style="{StaticResource AccentButton}" Click="Update_Click" Margin="0,0,6,0"
|
||||||
|
|||||||
@@ -71,11 +71,14 @@ public partial class SettingsWindow : Window
|
|||||||
? HotkeyHintText
|
? HotkeyHintText
|
||||||
: "De toetsenbordhaak kon niet worden gestart; de sneltoetsen werken nu niet.";
|
: "De toetsenbordhaak kon niet worden gestart; de sneltoetsen werken nu niet.";
|
||||||
Deactivated += (_, _) => StopListening();
|
Deactivated += (_, _) => StopListening();
|
||||||
|
App.Instance.Updates.AvailabilityChanged += OnUpdateAvailabilityChanged;
|
||||||
Closed += (_, _) =>
|
Closed += (_, _) =>
|
||||||
{
|
{
|
||||||
StopListening();
|
StopListening();
|
||||||
_updateCts?.Cancel();
|
_updateCts?.Cancel();
|
||||||
|
App.Instance.Updates.AvailabilityChanged -= OnUpdateAvailabilityChanged;
|
||||||
};
|
};
|
||||||
|
ShowUpdateBadge();
|
||||||
|
|
||||||
CurrentVersionText.Text = $"Geïnstalleerde versie: {UpdateChecker.CurrentVersion}";
|
CurrentVersionText.Text = $"Geïnstalleerde versie: {UpdateChecker.CurrentVersion}";
|
||||||
LoadFrom(settings);
|
LoadFrom(settings);
|
||||||
@@ -94,6 +97,7 @@ public partial class SettingsWindow : Window
|
|||||||
ActionOpenRadio.IsChecked = settings.Action == CaptureAction.OpenEditor;
|
ActionOpenRadio.IsChecked = settings.Action == CaptureAction.OpenEditor;
|
||||||
ActionNothingRadio.IsChecked = settings.Action == CaptureAction.Nothing;
|
ActionNothingRadio.IsChecked = settings.Action == CaptureAction.Nothing;
|
||||||
StraightHighlightBox.IsChecked = settings.StraightHighlight;
|
StraightHighlightBox.IsChecked = settings.StraightHighlight;
|
||||||
|
AutoUpdateCheckBox.IsChecked = settings.CheckForUpdates;
|
||||||
|
|
||||||
_snipFolder = settings.SnipSaveFolder;
|
_snipFolder = settings.SnipSaveFolder;
|
||||||
_videoFolder = settings.VideoSaveFolder;
|
_videoFolder = settings.VideoSaveFolder;
|
||||||
@@ -437,6 +441,7 @@ public partial class SettingsWindow : Window
|
|||||||
: ActionNothingRadio.IsChecked == true ? CaptureAction.Nothing
|
: ActionNothingRadio.IsChecked == true ? CaptureAction.Nothing
|
||||||
: CaptureAction.Notify;
|
: CaptureAction.Notify;
|
||||||
_settings.StraightHighlight = StraightHighlightBox.IsChecked == true;
|
_settings.StraightHighlight = StraightHighlightBox.IsChecked == true;
|
||||||
|
_settings.CheckForUpdates = AutoUpdateCheckBox.IsChecked == true;
|
||||||
StopListening();
|
StopListening();
|
||||||
_settings.SnipHotkey = _hotkeys["Snip"];
|
_settings.SnipHotkey = _hotkeys["Snip"];
|
||||||
_settings.WindowHotkey = _hotkeys["Window"];
|
_settings.WindowHotkey = _hotkeys["Window"];
|
||||||
@@ -505,6 +510,11 @@ public partial class SettingsWindow : Window
|
|||||||
|
|
||||||
private void CheckUpdate_Click(object sender, RoutedEventArgs e) => _ = CheckForUpdatesAsync();
|
private void CheckUpdate_Click(object sender, RoutedEventArgs e) => _ = CheckForUpdatesAsync();
|
||||||
|
|
||||||
|
private void OnUpdateAvailabilityChanged(object? sender, EventArgs e) => ShowUpdateBadge();
|
||||||
|
|
||||||
|
private void ShowUpdateBadge() =>
|
||||||
|
UpdateTabBadge.Visibility = App.Instance.Updates.IsUpdateAvailable ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
|
||||||
private void ReleasePage_Click(object sender, RoutedEventArgs e) => OpenUrl(UpdateChecker.ReleasesPage);
|
private void ReleasePage_Click(object sender, RoutedEventArgs e) => OpenUrl(UpdateChecker.ReleasesPage);
|
||||||
|
|
||||||
private async System.Threading.Tasks.Task CheckForUpdatesAsync()
|
private async System.Threading.Tasks.Task CheckForUpdatesAsync()
|
||||||
@@ -548,6 +558,7 @@ public partial class SettingsWindow : Window
|
|||||||
{
|
{
|
||||||
Version current = UpdateChecker.CurrentVersion;
|
Version current = UpdateChecker.CurrentVersion;
|
||||||
List<ReleaseInfo> newer = releases.Where(r => r.Version > current).ToList();
|
List<ReleaseInfo> newer = releases.Where(r => r.Version > current).ToList();
|
||||||
|
App.Instance.Updates.Report(newer.FirstOrDefault());
|
||||||
if (newer.Count == 0)
|
if (newer.Count == 0)
|
||||||
{
|
{
|
||||||
UpdateStatus.Text = releases.Count == 0
|
UpdateStatus.Text = releases.Count == 0
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Kniip.Update;
|
||||||
|
|
||||||
|
/// <summary>Kijkt op de achtergrond of er een nieuwere versie is en meldt dat aan de vensters.</summary>
|
||||||
|
public sealed class UpdateNotifier
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan FirstCheckDelay = TimeSpan.FromSeconds(10);
|
||||||
|
private static readonly TimeSpan CheckInterval = TimeSpan.FromHours(24);
|
||||||
|
|
||||||
|
private readonly DispatcherTimer _timer = new();
|
||||||
|
private bool _enabled;
|
||||||
|
private bool _checking;
|
||||||
|
|
||||||
|
public UpdateNotifier()
|
||||||
|
{
|
||||||
|
_timer.Tick += async (_, _) =>
|
||||||
|
{
|
||||||
|
_timer.Interval = CheckInterval;
|
||||||
|
await CheckAsync();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Nieuwste versie die nieuwer is dan deze exe; null als er niets nieuws is.</summary>
|
||||||
|
public ReleaseInfo? Available { get; private set; }
|
||||||
|
|
||||||
|
public bool IsUpdateAvailable => Available != null;
|
||||||
|
|
||||||
|
/// <summary>Wordt op de UI-thread afgevuurd als <see cref="Available"/> verandert.</summary>
|
||||||
|
public event EventHandler? AvailabilityChanged;
|
||||||
|
|
||||||
|
public void Apply(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled == _enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_enabled = enabled;
|
||||||
|
_timer.Stop();
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
_timer.Interval = FirstCheckDelay;
|
||||||
|
_timer.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Report(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resultaat van een controle elders (de tab Update), zodat de melding gelijkloopt.</summary>
|
||||||
|
public void Report(ReleaseInfo? newest)
|
||||||
|
{
|
||||||
|
if (newest != null && newest.Version <= UpdateChecker.CurrentVersion)
|
||||||
|
{
|
||||||
|
newest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = Available?.Version != newest?.Version;
|
||||||
|
Available = newest;
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
AvailabilityChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckAsync()
|
||||||
|
{
|
||||||
|
if (_checking)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_checking = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IReadOnlyList<ReleaseInfo> releases = await UpdateChecker.GetReleasesAsync();
|
||||||
|
if (_enabled)
|
||||||
|
{
|
||||||
|
Report(releases.FirstOrDefault(r => r.Version > UpdateChecker.CurrentVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UpdateException)
|
||||||
|
{
|
||||||
|
// Een mislukte achtergrondcontrole is geen melding waard; de volgende poging komt vanzelf.
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_checking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user