Files
DeskCore/Settings.cs
T
2026-10-03 19:51:56 +00:00

304 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DeskCore;
public enum DesignMode { System, Light, Dark }
public enum ViewMode { Small, Medium, Large, List }
public class CategoryMeta
{
public string? Glyph { get; set; }
public string? Color { get; set; }
// Die folgenden Box-Felder stammen aus Version 1.0.x und werden nur noch beim Umstieg auf FenceDef gelesen
public bool Fence { get; set; }
public double? FenceX { get; set; }
public double? FenceY { get; set; }
public double FenceW { get; set; } = 340;
public double FenceH { get; set; } = 260;
public bool FenceCollapsed { get; set; }
}
/// <summary>Eine Start-Gruppe: ein Klick startet mehrere Programme nacheinander.</summary>
public class LaunchGroup
{
public string Name { get; set; } = "";
/// <summary>Pfade in Startreihenfolge (meist Verknüpfungen aus den Kategorien).</summary>
public List<string> Items { get; set; } = [];
/// <summary>Wartezeit zwischen zwei Programmen in Sekunden.</summary>
public double DelaySeconds { get; set; } = 1;
public string? Color { get; set; }
}
public class SortRule
{
/// <summary>"Name" = Name enthält …, "Typ" = Dateityp ist … (z. B. "Bilder" oder "pdf, docx")</summary>
public string Kind { get; set; } = "Name";
public string Contains { get; set; } = "";
public string Category { get; set; } = "";
[JsonIgnore] public string KindText => Kind == "Typ" ? "Dateityp ist" : "Name enthält";
}
/// <summary>Ein Tab einer Desktop-Box: eine Kategorie oder ein beliebiger Ordner ("Ordner-Portal").</summary>
public class FenceTab
{
public const string CategoryKind = "Kategorie", FolderKind = "Ordner";
public string Kind { get; set; } = CategoryKind;
/// <summary>Kategoriename bzw. Ordnerpfad.</summary>
public string Value { get; set; } = "";
[JsonIgnore] public bool IsFolder => Kind == FolderKind;
[JsonIgnore] public string Title => IsFolder ? Path.GetFileName(Value.TrimEnd('\\')) is { Length: > 0 } n ? n : Value : Value;
}
/// <summary>Eine Desktop-Box mit Position, Tabs und Aussehen.</summary>
public class FenceDef
{
public string Id { get; set; } = Guid.NewGuid().ToString("N");
public List<FenceTab> Tabs { get; set; } = [];
public int ActiveTab { get; set; }
public double? X { get; set; }
public double? Y { get; set; }
public double W { get; set; } = 340;
public double H { get; set; } = 260;
public bool Collapsed { get; set; }
/// <summary>Gesperrt: nicht verschieb- oder vergrößerbar.</summary>
public bool Locked { get; set; }
/// <summary>Hintergrundfarbe (#RRGGBB) oder null = passend zum Design.</summary>
public string? Background { get; set; }
/// <summary>Deckkraft des Hintergrunds 0.2–1.</summary>
public double Opacity { get; set; } = 0.92;
/// <summary>Milchglas-Effekt (Hintergrund verschwommen).</summary>
public bool Blur { get; set; }
public ViewMode IconSize { get; set; } = ViewMode.Medium;
/// <summary>"Name", "Typ" oder "Datum"</summary>
public string Sort { get; set; } = "Name";
/// <summary>Icon-Tönung (#RRGGBB) oder null.</summary>
public string? Tint { get; set; }
/// <summary>Desktop-Seite, auf der die Box liegt (0 = erste Seite).</summary>
public int Page { get; set; }
[JsonIgnore] public FenceTab? Active => Tabs.Count == 0 ? null : Tabs[Math.Clamp(ActiveTab, 0, Tabs.Count - 1)];
}
/// <summary>Gespeicherte Anordnung aller Desktop-Boxen.</summary>
public class FenceSnapshot
{
public string Name { get; set; } = "";
public DateTime Created { get; set; } = DateTime.Now;
public List<FenceDef> Fences { get; set; } = [];
[JsonIgnore] public string Info => $"{Fences.Count} {(Fences.Count == 1 ? "Box" : "Boxen")} · gespeichert am {Created:dd.MM.yyyy HH:mm}";
}
/// <summary>Alle Einstellungen, gespeichert in %APPDATA%\DeskCore\settings.json.</summary>
public class AppSettings
{
public DesignMode Design { get; set; } = DesignMode.System;
public ViewMode View { get; set; } = ViewMode.Medium;
public bool TrayEnabled { get; set; } = true;
public bool CloseToTray { get; set; } = true;
public bool TrayHintShown { get; set; }
public bool HotkeyEnabled { get; set; } = true;
public int HotkeyModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift;
public int HotkeyKey { get; set; } = 0x44; // D
public bool AutoSort { get; set; }
public bool AutoSortRemoveFromDesktop { get; set; }
public List<SortRule> Rules { get; set; } = [];
public bool FencesEnabled { get; set; } = true;
public List<FenceDef> Fences { get; set; } = [];
public List<FenceSnapshot> Snapshots { get; set; } = [];
/// <summary>Box-Positionen je Bildschirm-Anordnung (Signatur → Box-Id → x, y, w, h).</summary>
public Dictionary<string, Dictionary<string, double[]>> ScreenLayouts { get; set; } = [];
/// <summary>Chamäleon: Boxen blenden sich aus, bis die Maus darüberfährt.</summary>
public bool FenceChameleon { get; set; }
/// <summary>Doppelklick auf eine freie Desktop-Stelle blendet Icons und Boxen aus/ein.</summary>
public bool QuickHide { get; set; } = true;
/// <summary>Peek: Tastenkürzel holt alle Boxen vor die Fenster.</summary>
public bool PeekEnabled { get; set; } = true;
public int PeekModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift;
public int PeekKey { get; set; } = 0x42; // B wie "Boxen"
/// <summary>Desktop-Seiten: jede Seite hat ihre eigenen Boxen.</summary>
public List<string> Pages { get; set; } = ["Seite 1"];
public int CurrentPage { get; set; }
/// <summary>Mausrad über einer freien Desktop-Stelle wechselt die Seite.</summary>
public bool PageWheel { get; set; } = true;
/// <summary>Rechte Maustaste auf freier Desktop-Fläche ziehen → neue Box.</summary>
public bool DrawFences { get; set; } = true;
/// <summary>Tönung der echten Desktop-Icons (#RRGGBB) oder null = aus.</summary>
public string? DesktopTint { get; set; }
public double DesktopTintStrength { get; set; } = 0.55;
/// <summary>Verknüpfungen zu deinstallierten Programmen automatisch entfernen.</summary>
public bool RemoveDeadShortcuts { get; set; } = true;
/// <summary>Tote Verknüpfungen, die der Nutzer per Rückgängig behalten wollte.</summary>
public List<string> KeptShortcuts { get; set; } = [];
// Fenster merken (WPF-Einheiten)
public double? WindowLeft { get; set; }
public double? WindowTop { get; set; }
public double? WindowWidth { get; set; }
public double? WindowHeight { get; set; }
public bool WindowMaximized { get; set; }
// Updates
public bool AutoUpdateCheck { get; set; } = true;
public DateTime? LastUpdateCheck { get; set; }
public string? SkippedVersion { get; set; }
public List<string> CategoryOrder { get; set; } = [];
public List<LaunchGroup> LaunchGroups { get; set; } = [];
public Dictionary<string, CategoryMeta> Categories { get; set; } = new(StringComparer.OrdinalIgnoreCase);
// ---------- Laden/Speichern ----------
public static readonly string Folder = Migration.Pick(Environment.SpecialFolder.ApplicationData);
private static readonly string FilePath = Path.Combine(Folder, "settings.json");
public static readonly JsonSerializerOptions Json = new()
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() },
};
public static AppSettings Current { get; private set; } = Load();
public static event Action? Saved;
private static AppSettings Load()
{
AppSettings s;
try
{
s = File.Exists(FilePath)
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(FilePath), Json) ?? new()
: new();
}
catch { s = new(); }
s.Categories = new(s.Categories ?? [], StringComparer.OrdinalIgnoreCase);
s.Rules ??= [];
s.CategoryOrder ??= [];
s.KeptShortcuts ??= [];
s.LaunchGroups ??= [];
s.Fences ??= [];
s.Snapshots ??= [];
s.ScreenLayouts ??= [];
if (s.Pages is null || s.Pages.Count == 0) s.Pages = ["Seite 1"];
s.CurrentPage = Math.Clamp(s.CurrentPage, 0, s.Pages.Count - 1);
// Umstieg von 1.0.x: Box gehörte fest zu einer Kategorie → eigenständige Box mit einem Tab
foreach (var (name, meta) in s.Categories)
{
if (!meta.Fence) continue;
s.Fences.Add(new FenceDef
{
Tabs = [new FenceTab { Value = name }],
X = meta.FenceX, Y = meta.FenceY, W = meta.FenceW, H = meta.FenceH, Collapsed = meta.FenceCollapsed,
});
meta.Fence = false;
}
// Übernahme der alten Einstellung aus Version 2
var old = Path.Combine(Folder, "design.txt");
if (File.Exists(old))
{
try
{
var v = File.ReadAllText(old).Trim();
s.Design = v == "Light" ? DesignMode.Light : v == "Dark" ? DesignMode.Dark : DesignMode.System;
File.Delete(old);
}
catch { }
}
return s;
}
public void Save()
{
try
{
Directory.CreateDirectory(Folder);
File.WriteAllText(FilePath, JsonSerializer.Serialize(this, Json));
}
catch { }
Saved?.Invoke();
}
public CategoryMeta Meta(string category)
{
if (!Categories.TryGetValue(category, out var m))
Categories[category] = m = new CategoryMeta();
return m;
}
public void RenameCategory(string oldName, string newName)
{
if (Categories.Remove(oldName, out var m)) Categories[newName] = m;
for (int i = 0; i < CategoryOrder.Count; i++)
if (string.Equals(CategoryOrder[i], oldName, StringComparison.OrdinalIgnoreCase)) CategoryOrder[i] = newName;
foreach (var r in Rules)
if (string.Equals(r.Category, oldName, StringComparison.OrdinalIgnoreCase)) r.Category = newName;
foreach (var t in Fences.SelectMany(f => f.Tabs))
if (!t.IsFolder && string.Equals(t.Value, oldName, StringComparison.OrdinalIgnoreCase)) t.Value = newName;
// Start-Gruppen zeigen auf Dateien in der Kategorie → Pfade mit umbenennen
var oldDir = Storage.CategoryPath(oldName).TrimEnd('\\') + "\\";
var newDir = Storage.CategoryPath(newName).TrimEnd('\\') + "\\";
foreach (var g in LaunchGroups)
for (int i = 0; i < g.Items.Count; i++)
if (g.Items[i].StartsWith(oldDir, StringComparison.OrdinalIgnoreCase))
g.Items[i] = newDir + g.Items[i][oldDir.Length..];
}
/// <summary>Wird diese Kategorie in irgendeiner Box angezeigt?</summary>
public bool HasFence(string category) =>
Fences.Any(f => f.Tabs.Any(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase)));
/// <summary>Kategorie als eigene Box anzeigen bzw. aus allen Boxen entfernen.</summary>
public void SetFence(string category, bool on)
{
if (on)
{
if (!HasFence(category)) Fences.Add(new FenceDef { Page = CurrentPage, Tabs = [new FenceTab { Value = category }] });
FencesEnabled = true;
}
else
{
foreach (var f in Fences)
f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
Fences.RemoveAll(f => f.Tabs.Count == 0);
}
}
/// <summary>Tiefe Kopie (für Snapshots).</summary>
public static List<FenceDef> CloneFences(List<FenceDef> fences) =>
JsonSerializer.Deserialize<List<FenceDef>>(JsonSerializer.Serialize(fences, Json), Json) ?? [];
/// <summary>Datei wurde umbenannt/verschoben → Start-Gruppen zeigen auf den neuen Pfad.</summary>
public void ReplaceLaunchPath(string oldPath, string newPath)
{
bool changed = false;
foreach (var g in LaunchGroups)
for (int i = 0; i < g.Items.Count; i++)
if (string.Equals(g.Items[i], oldPath, StringComparison.OrdinalIgnoreCase)) { g.Items[i] = newPath; changed = true; }
if (changed) Save();
}
public void RemoveCategory(string name)
{
foreach (var f in Fences)
f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, name, StringComparison.OrdinalIgnoreCase));
Fences.RemoveAll(f => f.Tabs.Count == 0);
Categories.Remove(name);
CategoryOrder.RemoveAll(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase));
Rules.RemoveAll(r => string.Equals(r.Category, name, StringComparison.OrdinalIgnoreCase));
}
}