diff --git a/LanMountainDesktop/Models/ComponentSettingsSnapshot.cs b/LanMountainDesktop/Models/ComponentSettingsSnapshot.cs index ff15f1a..96c8e5c 100644 --- a/LanMountainDesktop/Models/ComponentSettingsSnapshot.cs +++ b/LanMountainDesktop/Models/ComponentSettingsSnapshot.cs @@ -64,6 +64,7 @@ public sealed class ComponentSettingsSnapshot public string Stcn24ForumSourceType { get; set; } = Stcn24ForumSourceTypes.LatestCreated; + public ComponentSettingsSnapshot Clone() { var clone = (ComponentSettingsSnapshot)MemberwiseClone(); @@ -91,6 +92,9 @@ public sealed class ComponentSettingsSnapshot clone.WorldClockTimeZoneIds = WorldClockTimeZoneIds is { Count: > 0 } ? new List(WorldClockTimeZoneIds) : []; + clone.OfficeRecentDocumentsEnabledSources = OfficeRecentDocumentsEnabledSources is not null + ? new List(OfficeRecentDocumentsEnabledSources) + : null; return clone; } diff --git a/LanMountainDesktop/Models/OfficeRecentDocumentSourceTypes.cs b/LanMountainDesktop/Models/OfficeRecentDocumentSourceTypes.cs new file mode 100644 index 0000000..5f09879 --- /dev/null +++ b/LanMountainDesktop/Models/OfficeRecentDocumentSourceTypes.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LanMountainDesktop.Models; + +public static class OfficeRecentDocumentSourceTypes +{ + public const string Registry = "registry"; + public const string RecentFolders = "recent_folders"; + public const string JumpLists = "jump_lists"; + + public static IReadOnlyList SupportedValues { get; } = + [ + Registry, + RecentFolders, + JumpLists + ]; + + public static IReadOnlyList DefaultValues => SupportedValues; + + public static IReadOnlyList NormalizeValues(IEnumerable? values, bool useDefaultWhenEmpty) + { + if (values is null) + { + return useDefaultWhenEmpty ? DefaultValues : Array.Empty(); + } + + var normalized = values + .Select(NormalizeValue) + .OfType() + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (normalized.Length == 0 && useDefaultWhenEmpty) + { + return DefaultValues; + } + + return normalized; + } + + private static string? NormalizeValue(string? value) + { + return value?.Trim().ToLowerInvariant() switch + { + Registry => Registry, + RecentFolders => RecentFolders, + JumpLists => JumpLists, + _ => null + }; + } +} diff --git a/LanMountainDesktop/Services/DesktopComponentEditorRegistryFactory.cs b/LanMountainDesktop/Services/DesktopComponentEditorRegistryFactory.cs index 212a539..4e35315 100644 --- a/LanMountainDesktop/Services/DesktopComponentEditorRegistryFactory.cs +++ b/LanMountainDesktop/Services/DesktopComponentEditorRegistryFactory.cs @@ -75,6 +75,9 @@ public static class DesktopComponentEditorRegistryFactory [BuiltInComponentIds.DesktopRemovableStorage] = new( BuiltInComponentIds.DesktopRemovableStorage, context => new RemovableStorageComponentEditor(context)), + [BuiltInComponentIds.DesktopOfficeRecentDocuments] = new( + BuiltInComponentIds.DesktopOfficeRecentDocuments, + context => new OfficeRecentDocumentsComponentEditor(context)), [BuiltInComponentIds.DesktopWeather] = CreateWeatherRegistration(BuiltInComponentIds.DesktopWeather), [BuiltInComponentIds.DesktopWeatherClock] = CreateWeatherRegistration(BuiltInComponentIds.DesktopWeatherClock), [BuiltInComponentIds.DesktopHourlyWeather] = CreateWeatherRegistration(BuiltInComponentIds.DesktopHourlyWeather), diff --git a/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs b/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs index 8900485..b0b6fcb 100644 --- a/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs +++ b/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs @@ -9,6 +9,7 @@ using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; +using LanMountainDesktop.Models; using Microsoft.Win32; using MudTools.OfficeInterop; using MudTools.OfficeInterop.Excel; @@ -18,7 +19,7 @@ namespace LanMountainDesktop.Services; public interface IOfficeRecentDocumentsService { - List GetRecentDocuments(int maxCount = 20); + List GetRecentDocuments(int maxCount = 20, IReadOnlyCollection? enabledSources = null); void OpenDocument(string filePath); } @@ -48,20 +49,38 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService @"\[T(?[0-9A-F]+)\]", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public List GetRecentDocuments(int maxCount = 20) + public List GetRecentDocuments(int maxCount = 20, IReadOnlyCollection? enabledSources = null) { var documents = new List(); + var normalizedSources = OfficeRecentDocumentSourceTypes.NormalizeValues( + enabledSources, + useDefaultWhenEmpty: enabledSources is null); - if (!OperatingSystem.IsWindows()) + if (!OperatingSystem.IsWindows() || normalizedSources.Count == 0) { return documents; } - TryGetFromRegistry(documents); - TryGetFromRecentFolders(documents); - TryGetFromJumpLists(documents); + var useRegistry = normalizedSources.Contains(OfficeRecentDocumentSourceTypes.Registry, StringComparer.OrdinalIgnoreCase); + var useRecentFolders = normalizedSources.Contains(OfficeRecentDocumentSourceTypes.RecentFolders, StringComparer.OrdinalIgnoreCase); + var useJumpLists = normalizedSources.Contains(OfficeRecentDocumentSourceTypes.JumpLists, StringComparer.OrdinalIgnoreCase); - if (documents.Count < maxCount) + if (useRegistry) + { + TryGetFromRegistry(documents); + } + + if (useRecentFolders) + { + TryGetFromRecentFolders(documents); + } + + if (useJumpLists) + { + TryGetFromJumpLists(documents); + } + + if (useRegistry && documents.Count < maxCount) { TryGetFromMudToolsInterop(documents); } diff --git a/LanMountainDesktop/Views/ComponentEditors/OfficeRecentDocumentsComponentEditor.axaml b/LanMountainDesktop/Views/ComponentEditors/OfficeRecentDocumentsComponentEditor.axaml new file mode 100644 index 0000000..8e22802 --- /dev/null +++ b/LanMountainDesktop/Views/ComponentEditors/OfficeRecentDocumentsComponentEditor.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/LanMountainDesktop/Views/ComponentEditors/OfficeRecentDocumentsComponentEditor.axaml.cs b/LanMountainDesktop/Views/ComponentEditors/OfficeRecentDocumentsComponentEditor.axaml.cs new file mode 100644 index 0000000..df0c0a3 --- /dev/null +++ b/LanMountainDesktop/Views/ComponentEditors/OfficeRecentDocumentsComponentEditor.axaml.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using Avalonia.Interactivity; +using LanMountainDesktop.ComponentSystem; +using LanMountainDesktop.Models; + +namespace LanMountainDesktop.Views.ComponentEditors; + +public partial class OfficeRecentDocumentsComponentEditor : ComponentEditorViewBase +{ + private bool _suppressEvents; + + public OfficeRecentDocumentsComponentEditor() + : this(null) + { + } + + public OfficeRecentDocumentsComponentEditor(DesktopComponentEditorContext? context) + : base(context) + { + InitializeComponent(); + ApplyState(); + } + + private void ApplyState() + { + var snapshot = LoadSnapshot(); + var enabledSources = OfficeRecentDocumentSourceTypes.NormalizeValues( + snapshot.OfficeRecentDocumentsEnabledSources, + useDefaultWhenEmpty: snapshot.OfficeRecentDocumentsEnabledSources is null); + + HeadlineTextBlock.Text = Context?.Definition.DisplayName ?? "Office Recent Documents"; + DescriptionTextBlock.Text = L( + "office_recent_documents.settings.desc", + "Choose which Windows and Office sources this widget should scan for recent documents."); + SourcesHeaderTextBlock.Text = L( + "office_recent_documents.settings.sources_title", + "Recent document sources"); + SourcesDescriptionTextBlock.Text = L( + "office_recent_documents.settings.sources_desc", + "You can combine multiple sources. Registry selection also keeps the Office interop MRU fallback available."); + RegistryCheckBox.Content = L( + "office_recent_documents.settings.source.registry", + "Office registry MRU"); + RecentFoldersCheckBox.Content = L( + "office_recent_documents.settings.source.recent_folders", + "Windows Recent folders"); + JumpListsCheckBox.Content = L( + "office_recent_documents.settings.source.jump_lists", + "Windows Jump Lists"); + HintTextBlock.Text = L( + "office_recent_documents.settings.hint", + "If you disable all sources, this widget will stay empty until at least one source is enabled again."); + + _suppressEvents = true; + RegistryCheckBox.IsChecked = enabledSources.Contains(OfficeRecentDocumentSourceTypes.Registry, StringComparer.OrdinalIgnoreCase); + RecentFoldersCheckBox.IsChecked = enabledSources.Contains(OfficeRecentDocumentSourceTypes.RecentFolders, StringComparer.OrdinalIgnoreCase); + JumpListsCheckBox.IsChecked = enabledSources.Contains(OfficeRecentDocumentSourceTypes.JumpLists, StringComparer.OrdinalIgnoreCase); + _suppressEvents = false; + } + + private void OnSourceSelectionChanged(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + if (_suppressEvents) + { + return; + } + + var selectedSources = new[] + { + RegistryCheckBox.IsChecked == true ? OfficeRecentDocumentSourceTypes.Registry : null, + RecentFoldersCheckBox.IsChecked == true ? OfficeRecentDocumentSourceTypes.RecentFolders : null, + JumpListsCheckBox.IsChecked == true ? OfficeRecentDocumentSourceTypes.JumpLists : null + } + .Where(static value => !string.IsNullOrWhiteSpace(value)) + .Cast() + .ToList(); + + var snapshot = LoadSnapshot(); + snapshot.OfficeRecentDocumentsEnabledSources = selectedSources; + SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.OfficeRecentDocumentsEnabledSources)); + } +} diff --git a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs index fbe19c7..a1f0489 100644 --- a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs @@ -4,14 +4,20 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; +using LanMountainDesktop.ComponentSystem; +using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget +public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, IComponentPlacementContextAware { private readonly IOfficeRecentDocumentsService _recentDocumentsService; + private readonly IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate(); private List _documents = new(); + private string _componentId = BuiltInComponentIds.DesktopOfficeRecentDocuments; + private string _placementId = string.Empty; + private IReadOnlyList _enabledSources = OfficeRecentDocumentSourceTypes.DefaultValues; private bool _isOnActivePage; private bool _isEditMode; private bool _isLoading; @@ -20,6 +26,7 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen { InitializeComponent(); _recentDocumentsService = new OfficeRecentDocumentsService(); + ReloadSettings(); } public void ApplyCellSize(double cellSize) @@ -44,6 +51,15 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopOfficeRecentDocuments + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + ReloadSettings(); + } + private async void LoadDocuments() { if (_isLoading) @@ -54,10 +70,12 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen try { _isLoading = true; + ReloadSettings(); StatusTextBlock.IsVisible = false; DocumentsItemsControl.ItemsSource = null; - _documents = await Task.Run(() => _recentDocumentsService.GetRecentDocuments(20)); + var enabledSources = _enabledSources.ToArray(); + _documents = await Task.Run(() => _recentDocumentsService.GetRecentDocuments(20, enabledSources)); if (_documents.Count == 0) { @@ -80,6 +98,14 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen } } + private void ReloadSettings() + { + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); + _enabledSources = OfficeRecentDocumentSourceTypes.NormalizeValues( + snapshot.OfficeRecentDocumentsEnabledSources, + useDefaultWhenEmpty: snapshot.OfficeRecentDocumentsEnabledSources is null); + } + private void UpdateDisplay() { var displayItems = _documents.Select(d => new OfficeRecentDocumentViewModel