From 398cfa310e8e82c40c0647a5bab8c8bdf5fa2d9f Mon Sep 17 00:00:00 2001 From: Maddo Date: Sun, 24 May 2026 19:07:17 +0200 Subject: [PATCH] Refactor path handling in UI components - Removed redundant folder and file opening methods from AiTabView and FaceAiTabView. - Introduced PathPickerField control to streamline path selection and opening functionality across multiple views. - Updated FaceAiTabView and GeneralTabView to utilize PathPickerField for source and destination path selection. - Created PathShellService to encapsulate logic for opening paths in the file explorer. - Simplified XAML structure by replacing manual grid definitions with PathPickerField components. - Removed unused namespaces and cleaned up code for better readability and maintainability. --- imagecatalog/AvaloniaViews/AiTabView.axaml | 73 ++--- imagecatalog/AvaloniaViews/AiTabView.axaml.cs | 56 ---- .../AvaloniaViews/FaceAiTabView.axaml | 201 +++++------- .../AvaloniaViews/FaceAiTabView.axaml.cs | 246 +-------------- .../AvaloniaViews/GeneralTabView.axaml | 47 +-- .../AvaloniaViews/GeneralTabView.axaml.cs | 46 --- imagecatalog/Controls/PathPickerField.axaml | 34 +++ .../Controls/PathPickerField.axaml.cs | 285 ++++++++++++++++++ .../Controls/PathPickerSelectionMode.cs | 8 + imagecatalog/Services/PathShellService.cs | 50 +++ 10 files changed, 484 insertions(+), 562 deletions(-) create mode 100644 imagecatalog/Controls/PathPickerField.axaml create mode 100644 imagecatalog/Controls/PathPickerField.axaml.cs create mode 100644 imagecatalog/Controls/PathPickerSelectionMode.cs create mode 100644 imagecatalog/Services/PathShellService.cs diff --git a/imagecatalog/AvaloniaViews/AiTabView.axaml b/imagecatalog/AvaloniaViews/AiTabView.axaml index 7c703df..5026497 100644 --- a/imagecatalog/AvaloniaViews/AiTabView.axaml +++ b/imagecatalog/AvaloniaViews/AiTabView.axaml @@ -1,6 +1,7 @@ @@ -31,25 +32,13 @@ FontWeight="SemiBold" /> - - - - - - + - - + @@ -108,22 +89,12 @@ - - - - - - + diff --git a/imagecatalog/AvaloniaViews/AiTabView.axaml.cs b/imagecatalog/AvaloniaViews/AiTabView.axaml.cs index 852cc63..f56e327 100644 --- a/imagecatalog/AvaloniaViews/AiTabView.axaml.cs +++ b/imagecatalog/AvaloniaViews/AiTabView.axaml.cs @@ -1,8 +1,4 @@ using Avalonia.Controls; -using Avalonia.Interactivity; -using System.Diagnostics; -using System.IO; - namespace ImageCatalog_2.AvaloniaViews; public partial class AiTabView : Avalonia.Controls.UserControl @@ -11,56 +7,4 @@ public partial class AiTabView : Avalonia.Controls.UserControl { InitializeComponent(); } - - private void OpenModelsFolder_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is DataModel model) - { - OpenInExplorer(model.ModelsFolderPath); - } - } - - private void OpenCsvOutputFolder_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is not DataModel model) - { - return; - } - - var directory = Path.GetDirectoryName(model.CsvOutputPath); - OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? model.CsvOutputPath : directory); - } - - private void OpenAiSourceFolder_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is DataModel model) - { - OpenInExplorer(model.DestinationPath); - } - } - - private static void OpenInExplorer(string? path) - { - if (string.IsNullOrWhiteSpace(path)) - { - return; - } - - var normalizedPath = path.Trim().Trim('"'); - try - { - if (File.Exists(normalizedPath)) - { - Process.Start("explorer.exe", $"/select,\"{normalizedPath}\""); - } - else if (Directory.Exists(normalizedPath)) - { - Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true }); - } - } - catch - { - // Ignore failures when opening Explorer. - } - } } diff --git a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml index 55e74f1..dc1aed0 100644 --- a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml +++ b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml @@ -1,6 +1,7 @@ - - - - - + - - - - - - + @@ -116,22 +104,12 @@ TextWrapping="Wrap" Opacity="0.75" /> - - - - - - + @@ -146,40 +124,24 @@ TextWrapping="Wrap" Opacity="0.8" /> - - - - - - + - - - - - - + @@ -193,68 +155,43 @@ - - - - - + - - - - - - + - - - - - - + - - - - - - + diff --git a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs index e8b1c95..0fbe36b 100644 --- a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs +++ b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs @@ -4,15 +4,11 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Media.Imaging; -using Avalonia.Platform.Storage; using Avalonia.Threading; using ImageCatalog_2.Models; using ImageCatalog_2.Services; -using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.IO; using System.Text; using System.Threading.Tasks; @@ -22,11 +18,9 @@ namespace ImageCatalog_2.AvaloniaViews; public partial class FaceAiTabView : Avalonia.Controls.UserControl { private INotifyPropertyChanged? _faceAiPropertySource; - private readonly PickerPreferenceService _pickerPreferenceService; public FaceAiTabView() { - _pickerPreferenceService = Program.ServiceProvider.GetRequiredService(); InitializeComponent(); DataContextChanged += OnDataContextChanged; } @@ -74,142 +68,6 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl }); } - private async void SelectFaceExecutable_Click(object? sender, RoutedEventArgs e) - { - var currentPath = DataContext is DataModel currentModel ? currentModel.FaceExecutablePath : null; - var folders = await OpenFolderPickerAsync("Seleziona la cartella Face Recognition Windows", PickerPreferenceKeys.FaceExecutableFolder, currentPath); - if (folders.Count > 0 && DataContext is DataModel model) - { - model.FaceExecutablePath = folders[0].Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceExecutableFolder, model.FaceExecutablePath); - } - } - - private async void SelectFaceOutputFolder_Click(object? sender, RoutedEventArgs e) - { - var currentPath = DataContext is DataModel currentModel ? currentModel.FaceOutputFolderPath : null; - var folders = await OpenFolderPickerAsync("Seleziona la cartella output per encodings e log", PickerPreferenceKeys.FaceOutputFolder, currentPath); - if (folders.Count > 0 && DataContext is DataModel model) - { - model.FaceOutputFolderPath = folders[0].Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceOutputFolder, model.FaceOutputFolderPath); - } - } - - private async void SelectFaceMatcherExecutable_Click(object? sender, RoutedEventArgs e) - { - var files = await OpenFilePickerAsync( - "Seleziona face_matcher.exe", - [new FilePickerFileType("Eseguibile") { Patterns = ["*.exe"] }], - PickerPreferenceKeys.FaceMatcherExecutable, - DataContext is DataModel currentModel ? currentModel.FaceMatcherExecutablePath : null); - - if (files.Count > 0 && DataContext is DataModel model) - { - model.FaceMatcherExecutablePath = files[0].Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherExecutable, model.FaceMatcherExecutablePath); - } - } - - private async void SelectFaceMatcherImage_Click(object? sender, RoutedEventArgs e) - { - var files = await OpenFilePickerAsync( - "Seleziona immagine per il match", - [new FilePickerFileType("Immagini") { Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] }], - PickerPreferenceKeys.FaceMatcherImage, - DataContext is DataModel currentModel ? currentModel.FaceMatcherSelectedImagePath : null); - - if (files.Count > 0 && DataContext is DataModel model) - { - model.FaceMatcherSelectedImagePath = files[0].Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherImage, model.FaceMatcherSelectedImagePath); - } - } - - private async void SelectFaceMatcherEncodings_Click(object? sender, RoutedEventArgs e) - { - var files = await OpenFilePickerAsync( - "Seleziona file encodings .pkl", - [new FilePickerFileType("Encodings") { Patterns = ["*.pkl"] }], - PickerPreferenceKeys.FaceMatcherEncodings, - DataContext is DataModel currentModel ? currentModel.FaceMatcherEncodingsPath : null); - - if (files.Count > 0 && DataContext is DataModel model) - { - model.FaceMatcherEncodingsPath = files[0].Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherEncodings, model.FaceMatcherEncodingsPath); - } - } - - private async void SelectFaceMatcherOutput_Click(object? sender, RoutedEventArgs e) - { - var file = await SaveFilePickerAsync( - "Seleziona output CSV del matcher", - "csv", - [new FilePickerFileType("CSV") { Patterns = ["*.csv"] }], - PickerPreferenceKeys.FaceMatcherOutput, - DataContext is DataModel currentModel ? currentModel.FaceMatcherOutputPath : null); - - if (file is not null && DataContext is DataModel model) - { - model.FaceMatcherOutputPath = file.Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherOutput, model.FaceMatcherOutputPath); - } - } - - private async void SelectFaceMatcherLog_Click(object? sender, RoutedEventArgs e) - { - var file = await SaveFilePickerAsync( - "Seleziona log TXT del matcher", - "txt", - [new FilePickerFileType("Log") { Patterns = ["*.txt", "*.log"] }], - PickerPreferenceKeys.FaceMatcherLog, - DataContext is DataModel currentModel ? currentModel.FaceMatcherLogPath : null); - - if (file is not null && DataContext is DataModel model) - { - model.FaceMatcherLogPath = file.Path.LocalPath; - _pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherLog, model.FaceMatcherLogPath); - } - } - - private void OpenFaceExecutableFolder_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceExecutablePathTextBox"); - - private void OpenFaceOutputFolder_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceOutputFolderTextBox"); - - private void OpenFaceMatcherExecutable_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherExecutablePathTextBox"); - - private void OpenFaceMatcherImage_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherImagePathTextBox"); - - private void OpenFaceMatcherEncodings_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherEncodingsPathTextBox"); - - private void OpenFaceMatcherOutput_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherOutputPathTextBox"); - - private void OpenFaceMatcherLog_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherLogPathTextBox"); - - private void OpenFaceDestinationFolder_Click(object? sender, RoutedEventArgs e) - { - string? path = null; - if (DataContext is DataModel model) - { - path = (model.DestinationPath ?? string.Empty).Trim(); - } - - if (string.IsNullOrWhiteSpace(path)) - { - return; - } - - if (Directory.Exists(path)) - { - OpenInExplorer(path); - return; - } - - var directory = Path.GetDirectoryName(path); - OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory); - } - private async void OpenFaceMatcherPreview_Click(object? sender, RoutedEventArgs e) { if (sender is not Button { Tag: FaceMatcherResultItem item }) @@ -444,14 +302,14 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl Grid.SetRow(footer, 2); var openFileButton = new Button { Content = "Apri file" }; - openFileButton.Click += (_, _) => OpenInExplorer(item.ResolvedImagePath); + openFileButton.Click += (_, _) => PathShellService.OpenInExplorer(item.ResolvedImagePath); footer.Children.Add(openFileButton); var openFolderButton = new Button { Content = "Apri cartella" }; openFolderButton.Click += (_, _) => { var directory = Path.GetDirectoryName(item.ResolvedImagePath); - OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? item.ResolvedImagePath : directory); + PathShellService.OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? item.ResolvedImagePath : directory); }; footer.Children.Add(openFolderButton); @@ -464,104 +322,4 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl return dialog; } - private void OpenFromTextBox(string textBoxName) - { - var textBox = this.FindControl(textBoxName); - var path = textBox?.Text?.Trim(); - if (string.IsNullOrWhiteSpace(path)) - { - return; - } - - if (Directory.Exists(path) || File.Exists(path)) - { - OpenInExplorer(path); - return; - } - - var directory = Path.GetDirectoryName(path); - OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory); - } - - private async Task> OpenFolderPickerAsync(string title, string preferenceKey, string? currentPath) - { - var topLevel = TopLevel.GetTopLevel(this); - var storageProvider = topLevel?.StorageProvider; - if (storageProvider is null) - { - return Array.Empty(); - } - - var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, preferenceKey, currentPath); - - return await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = title, - SuggestedStartLocation = suggestedStartLocation - }); - } - - private async Task> OpenFilePickerAsync(string title, IReadOnlyList fileTypes, string preferenceKey, string? currentPath) - { - var topLevel = TopLevel.GetTopLevel(this); - var storageProvider = topLevel?.StorageProvider; - if (storageProvider is null) - { - return Array.Empty(); - } - - var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, preferenceKey, currentPath); - - return await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - Title = title, - FileTypeFilter = fileTypes, - SuggestedStartLocation = suggestedStartLocation - }); - } - - private async Task SaveFilePickerAsync(string title, string defaultExtension, IReadOnlyList fileTypes, string preferenceKey, string? currentPath) - { - var topLevel = TopLevel.GetTopLevel(this); - var storageProvider = topLevel?.StorageProvider; - if (storageProvider is null) - { - return null; - } - - var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, preferenceKey, currentPath); - - return await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions - { - Title = title, - DefaultExtension = defaultExtension, - FileTypeChoices = fileTypes, - SuggestedStartLocation = suggestedStartLocation - }); - } - - private static void OpenInExplorer(string? path) - { - if (string.IsNullOrWhiteSpace(path)) - { - return; - } - - var normalizedPath = path.Trim().Trim('"'); - try - { - if (File.Exists(normalizedPath)) - { - Process.Start("explorer.exe", $"/select,\"{normalizedPath}\""); - } - else if (Directory.Exists(normalizedPath)) - { - Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true }); - } - } - catch - { - // Ignore failures when opening Explorer. - } - } } diff --git a/imagecatalog/AvaloniaViews/GeneralTabView.axaml b/imagecatalog/AvaloniaViews/GeneralTabView.axaml index 82979eb..ed0dd98 100644 --- a/imagecatalog/AvaloniaViews/GeneralTabView.axaml +++ b/imagecatalog/AvaloniaViews/GeneralTabView.axaml @@ -1,43 +1,24 @@ - - - - - - - - - - - - + + diff --git a/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs b/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs index cdcbec9..9670c2a 100644 --- a/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs +++ b/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs @@ -1,9 +1,4 @@ using Avalonia.Controls; -using Avalonia.Interactivity; -using System; -using System.Diagnostics; -using System.IO; - namespace ImageCatalog_2.AvaloniaViews; public partial class GeneralTabView : Avalonia.Controls.UserControl @@ -12,45 +7,4 @@ public partial class GeneralTabView : Avalonia.Controls.UserControl { InitializeComponent(); } - - private void OpenSourceFolder_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is DataModel model) - { - OpenInExplorer(model.SourcePath); - } - } - - private void OpenDestinationFolder_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is DataModel model) - { - OpenInExplorer(model.DestinationPath); - } - } - - private static void OpenInExplorer(string? path) - { - if (string.IsNullOrWhiteSpace(path)) - { - return; - } - - var normalizedPath = path.Trim().Trim('"'); - try - { - if (File.Exists(normalizedPath)) - { - Process.Start("explorer.exe", $"/select,\"{normalizedPath}\""); - } - else if (Directory.Exists(normalizedPath)) - { - Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true }); - } - } - catch - { - // Ignore failures when opening Explorer. - } - } } diff --git a/imagecatalog/Controls/PathPickerField.axaml b/imagecatalog/Controls/PathPickerField.axaml new file mode 100644 index 0000000..dfa6948 --- /dev/null +++ b/imagecatalog/Controls/PathPickerField.axaml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/imagecatalog/Controls/PathPickerField.axaml.cs b/imagecatalog/Controls/PathPickerField.axaml.cs new file mode 100644 index 0000000..6ec2538 --- /dev/null +++ b/imagecatalog/Controls/PathPickerField.axaml.cs @@ -0,0 +1,285 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Platform.Storage; +using ImageCatalog_2.Services; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace ImageCatalog_2.Controls; + +public partial class PathPickerField : UserControl +{ + private readonly PickerPreferenceService _pickerPreferenceService; + + public static readonly StyledProperty LabelProperty = + AvaloniaProperty.Register(nameof(Label), string.Empty); + + public static readonly StyledProperty TextProperty = + AvaloniaProperty.Register(nameof(Text), string.Empty, defaultBindingMode: BindingMode.TwoWay); + + public static readonly StyledProperty WatermarkProperty = + AvaloniaProperty.Register(nameof(Watermark), string.Empty); + + public static readonly StyledProperty PreferenceKeyProperty = + AvaloniaProperty.Register(nameof(PreferenceKey), string.Empty); + + public static readonly StyledProperty PickerTitleProperty = + AvaloniaProperty.Register(nameof(PickerTitle), string.Empty); + + public static readonly StyledProperty FileTypeNameProperty = + AvaloniaProperty.Register(nameof(FileTypeName), string.Empty); + + public static readonly StyledProperty FilePatternsProperty = + AvaloniaProperty.Register(nameof(FilePatterns), string.Empty); + + public static readonly StyledProperty DefaultExtensionProperty = + AvaloniaProperty.Register(nameof(DefaultExtension), string.Empty); + + public static readonly StyledProperty PickerModeProperty = + AvaloniaProperty.Register(nameof(PickerMode), PathPickerSelectionMode.Folder); + + public static readonly StyledProperty IsTextReadOnlyProperty = + AvaloniaProperty.Register(nameof(IsTextReadOnly), false); + + public static readonly StyledProperty ShowPickerButtonProperty = + AvaloniaProperty.Register(nameof(ShowPickerButton), true); + + public static readonly StyledProperty AppendDirectorySeparatorProperty = + AvaloniaProperty.Register(nameof(AppendDirectorySeparator), false); + + public PathPickerField() + { + _pickerPreferenceService = Program.ServiceProvider.GetRequiredService(); + InitializeComponent(); + } + + public string Label + { + get => GetValue(LabelProperty); + set => SetValue(LabelProperty, value); + } + + public string Text + { + get => GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public string Watermark + { + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); + } + + public string PreferenceKey + { + get => GetValue(PreferenceKeyProperty); + set => SetValue(PreferenceKeyProperty, value); + } + + public string PickerTitle + { + get => GetValue(PickerTitleProperty); + set => SetValue(PickerTitleProperty, value); + } + + public string FileTypeName + { + get => GetValue(FileTypeNameProperty); + set => SetValue(FileTypeNameProperty, value); + } + + public string FilePatterns + { + get => GetValue(FilePatternsProperty); + set => SetValue(FilePatternsProperty, value); + } + + public string DefaultExtension + { + get => GetValue(DefaultExtensionProperty); + set => SetValue(DefaultExtensionProperty, value); + } + + public PathPickerSelectionMode PickerMode + { + get => GetValue(PickerModeProperty); + set => SetValue(PickerModeProperty, value); + } + + public bool IsTextReadOnly + { + get => GetValue(IsTextReadOnlyProperty); + set => SetValue(IsTextReadOnlyProperty, value); + } + + public bool ShowPickerButton + { + get => GetValue(ShowPickerButtonProperty); + set => SetValue(ShowPickerButtonProperty, value); + } + + public bool AppendDirectorySeparator + { + get => GetValue(AppendDirectorySeparatorProperty); + set => SetValue(AppendDirectorySeparatorProperty, value); + } + + private async void PickPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var topLevel = TopLevel.GetTopLevel(this); + var storageProvider = topLevel?.StorageProvider; + if (storageProvider is null) + { + return; + } + + var selectedPath = await PickPathAsync(storageProvider); + if (string.IsNullOrWhiteSpace(selectedPath)) + { + return; + } + + Text = selectedPath; + + if (!string.IsNullOrWhiteSpace(PreferenceKey)) + { + _pickerPreferenceService.RememberPath(PreferenceKey, selectedPath); + } + } + + private void OpenPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + PathShellService.OpenInExplorer(Text); + } + + private async Task PickPathAsync(IStorageProvider storageProvider) + { + var suggestedStartLocation = await TryGetSuggestedStartLocationAsync(storageProvider); + var pickerTitle = ResolvePickerTitle(); + + switch (PickerMode) + { + case PathPickerSelectionMode.Folder: + { + var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = pickerTitle, + SuggestedStartLocation = suggestedStartLocation + }); + + return folders.Count == 0 + ? null + : NormalizeSelectedPath(folders[0].Path.LocalPath); + } + case PathPickerSelectionMode.OpenFile: + { + var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = pickerTitle, + SuggestedStartLocation = suggestedStartLocation, + FileTypeFilter = BuildFileTypes() + }); + + return files.Count == 0 + ? null + : files[0].Path.LocalPath; + } + case PathPickerSelectionMode.SaveFile: + { + var file = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions + { + Title = pickerTitle, + SuggestedStartLocation = suggestedStartLocation, + DefaultExtension = NormalizeDefaultExtension(DefaultExtension), + FileTypeChoices = BuildFileTypes() + }); + + return file?.Path.LocalPath; + } + default: + return null; + } + } + + private async Task TryGetSuggestedStartLocationAsync(IStorageProvider storageProvider) + { + if (string.IsNullOrWhiteSpace(PreferenceKey)) + { + return null; + } + + return await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, PreferenceKey, Text); + } + + private IReadOnlyList BuildFileTypes() + { + var patterns = (FilePatterns ?? string.Empty) + .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (patterns.Length == 0) + { + return Array.Empty(); + } + + var fileTypeName = string.IsNullOrWhiteSpace(FileTypeName) + ? "File" + : FileTypeName.Trim(); + + return + [ + new FilePickerFileType(fileTypeName) + { + Patterns = patterns.ToList() + } + ]; + } + + private string ResolvePickerTitle() + { + if (!string.IsNullOrWhiteSpace(PickerTitle)) + { + return PickerTitle; + } + + var cleanedLabel = string.IsNullOrWhiteSpace(Label) + ? "percorso" + : Label.Trim().TrimEnd(':'); + + return PickerMode == PathPickerSelectionMode.SaveFile + ? $"Salva {cleanedLabel.ToLowerInvariant()}" + : $"Seleziona {cleanedLabel.ToLowerInvariant()}"; + } + + private string NormalizeSelectedPath(string selectedPath) + { + if (PickerMode != PathPickerSelectionMode.Folder || !AppendDirectorySeparator) + { + return selectedPath; + } + + if (string.IsNullOrWhiteSpace(selectedPath)) + { + return string.Empty; + } + + return selectedPath.EndsWith(Path.DirectorySeparatorChar) + || selectedPath.EndsWith(Path.AltDirectorySeparatorChar) + ? selectedPath + : selectedPath + Path.DirectorySeparatorChar; + } + + private static string NormalizeDefaultExtension(string extension) + { + if (string.IsNullOrWhiteSpace(extension)) + { + return string.Empty; + } + + return extension.Trim().TrimStart('.'); + } +} \ No newline at end of file diff --git a/imagecatalog/Controls/PathPickerSelectionMode.cs b/imagecatalog/Controls/PathPickerSelectionMode.cs new file mode 100644 index 0000000..27d456e --- /dev/null +++ b/imagecatalog/Controls/PathPickerSelectionMode.cs @@ -0,0 +1,8 @@ +namespace ImageCatalog_2.Controls; + +public enum PathPickerSelectionMode +{ + Folder, + OpenFile, + SaveFile +} \ No newline at end of file diff --git a/imagecatalog/Services/PathShellService.cs b/imagecatalog/Services/PathShellService.cs new file mode 100644 index 0000000..fcba122 --- /dev/null +++ b/imagecatalog/Services/PathShellService.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using System.IO; + +namespace ImageCatalog_2.Services; + +public static class PathShellService +{ + public static void OpenInExplorer(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return; + } + + var normalizedPath = path.Trim().Trim('"'); + + try + { + if (File.Exists(normalizedPath)) + { + Process.Start("explorer.exe", $"/select,\"{normalizedPath}\""); + return; + } + + if (Directory.Exists(normalizedPath)) + { + Process.Start(new ProcessStartInfo + { + FileName = normalizedPath, + UseShellExecute = true + }); + return; + } + + var containingDirectory = Path.GetDirectoryName(normalizedPath); + if (!string.IsNullOrWhiteSpace(containingDirectory) && Directory.Exists(containingDirectory)) + { + Process.Start(new ProcessStartInfo + { + FileName = containingDirectory, + UseShellExecute = true + }); + } + } + catch + { + // Ignore failures when opening Explorer. + } + } +} \ No newline at end of file