A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

377 lines
13 KiB

using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Threading;
namespace Avalonia.Dialogs
{
internal class ManagedFileChooserViewModel : InternalViewModelBase
{
private readonly ManagedFileDialogOptions _options;
public event Action CancelRequested;
public event Action<string[]> CompleteRequested;
public AvaloniaList<ManagedFileChooserItemViewModel> QuickLinks { get; } =
new AvaloniaList<ManagedFileChooserItemViewModel>();
public AvaloniaList<ManagedFileChooserItemViewModel> Items { get; } =
new AvaloniaList<ManagedFileChooserItemViewModel>();
public AvaloniaList<ManagedFileChooserFilterViewModel> Filters { get; } =
new AvaloniaList<ManagedFileChooserFilterViewModel>();
public AvaloniaList<ManagedFileChooserItemViewModel> SelectedItems { get; } =
new AvaloniaList<ManagedFileChooserItemViewModel>();
string _location;
string _fileName;
private bool _showHiddenFiles;
private ManagedFileChooserFilterViewModel _selectedFilter;
private bool _selectingDirectory;
private bool _savingFile;
private bool _scheduledSelectionValidation;
private bool _alreadyCancelled = false;
private string _defaultExtension;
private CompositeDisposable _disposables;
public string Location
{
get => _location;
private set => this.RaiseAndSetIfChanged(ref _location, value);
}
public string FileName
{
get => _fileName;
private set => this.RaiseAndSetIfChanged(ref _fileName, value);
}
public bool SelectingFolder => _selectingDirectory;
public bool ShowFilters { get; }
public SelectionMode SelectionMode { get; }
public string Title { get; }
public int QuickLinksSelectedIndex
{
get
{
for (var index = 0; index < QuickLinks.Count; index++)
{
var i = QuickLinks[index];
if (i.Path == Location)
{
return index;
}
}
return -1;
}
set => this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
}
public ManagedFileChooserFilterViewModel SelectedFilter
{
get => _selectedFilter;
set
{
this.RaiseAndSetIfChanged(ref _selectedFilter, value);
Refresh();
}
}
public bool ShowHiddenFiles
{
get => _showHiddenFiles;
set
{
this.RaiseAndSetIfChanged(ref _showHiddenFiles, value);
Refresh();
}
}
private void RefreshQuickLinks(ManagedFileChooserSources quickSources)
{
QuickLinks.Clear();
QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
}
public ManagedFileChooserViewModel(FileSystemDialog dialog, ManagedFileDialogOptions options)
{
_options = options;
_disposables = new CompositeDisposable();
var quickSources = AvaloniaLocator.Current
.GetService<ManagedFileChooserSources>()
?? new ManagedFileChooserSources();
var sub1 = AvaloniaLocator.Current
.GetService<IMountedVolumeInfoProvider>()
.Listen(ManagedFileChooserSources.MountedVolumes);
var sub2 = Observable.FromEventPattern(ManagedFileChooserSources.MountedVolumes,
nameof(ManagedFileChooserSources.MountedVolumes.CollectionChanged))
.ObserveOn(AvaloniaScheduler.Instance)
.Subscribe(x => RefreshQuickLinks(quickSources));
_disposables.Add(sub1);
_disposables.Add(sub2);
CompleteRequested += delegate { _disposables?.Dispose(); };
CancelRequested += delegate { _disposables?.Dispose(); };
RefreshQuickLinks(quickSources);
Title = dialog.Title ?? (
dialog is OpenFileDialog ? "Open file"
: dialog is SaveFileDialog ? "Save file"
: dialog is OpenFolderDialog ? "Select directory"
: throw new ArgumentException(nameof(dialog)));
var directory = dialog.Directory;
if (directory == null || !Directory.Exists(directory))
{
directory = Directory.GetCurrentDirectory();
}
if (dialog is FileDialog fd)
{
if (fd.Filters?.Count > 0)
{
Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f)));
_selectedFilter = Filters[0];
ShowFilters = true;
}
if (dialog is OpenFileDialog ofd)
{
if (ofd.AllowMultiple)
{
SelectionMode = SelectionMode.Multiple;
}
}
}
_selectingDirectory = dialog is OpenFolderDialog;
if (dialog is SaveFileDialog sfd)
{
_savingFile = true;
_defaultExtension = sfd.DefaultExtension;
FileName = sfd.InitialFileName;
}
Navigate(directory, (dialog as FileDialog)?.InitialFileName);
SelectedItems.CollectionChanged += OnSelectionChangedAsync;
}
public void EnterPressed()
{
if (Directory.Exists(Location))
{
Navigate(Location);
}
else if (File.Exists(Location))
{
CompleteRequested?.Invoke(new[] { Location });
}
}
private async void OnSelectionChangedAsync(object sender, NotifyCollectionChangedEventArgs e)
{
if (_scheduledSelectionValidation)
{
return;
}
_scheduledSelectionValidation = true;
await Dispatcher.UIThread.InvokeAsync(() =>
{
try
{
if (_selectingDirectory)
{
SelectedItems.Clear();
}
else
{
if (!_options.AllowDirectorySelection)
{
var invalidItems = SelectedItems.Where(i => i.ItemType == ManagedFileChooserItemType.Folder)
.ToList();
foreach (var item in invalidItems)
SelectedItems.Remove(item);
}
if (!_selectingDirectory)
{
var selectedItem = SelectedItems.FirstOrDefault();
if (selectedItem != null)
{
FileName = selectedItem.DisplayName;
}
}
}
}
finally
{
_scheduledSelectionValidation = false;
}
});
}
void NavigateRoot(string initialSelectionName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName);
}
else
{
Navigate("/", initialSelectionName);
}
}
public void Refresh() => Navigate(Location);
public void Navigate(string path, string initialSelectionName = null)
{
if (!Directory.Exists(path))
{
NavigateRoot(initialSelectionName);
}
else
{
Location = path;
Items.Clear();
SelectedItems.Clear();
try
{
var infos = new DirectoryInfo(path).EnumerateFileSystemInfos();
if (!ShowHiddenFiles)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) == 0);
}
else
{
infos = infos.Where(i => !i.Name.StartsWith("."));
}
}
if (SelectedFilter != null)
{
infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name));
}
Items.AddRange(infos.Where(x =>
{
if (_selectingDirectory)
{
if (!(x is DirectoryInfo))
{
return false;
}
}
return true;
})
.Where(x => x.Exists)
.Select(info => new ManagedFileChooserItemViewModel
{
DisplayName = info.Name,
Path = info.FullName,
Type = info is FileInfo ? info.Extension : "File Folder",
ItemType = info is FileInfo ? ManagedFileChooserItemType.File
: ManagedFileChooserItemType.Folder,
Size = info is FileInfo f ? f.Length : 0,
Modified = info.LastWriteTime
})
.OrderByDescending(x => x.ItemType == ManagedFileChooserItemType.Folder)
.ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase));
if (initialSelectionName != null)
{
var sel = Items.FirstOrDefault(i => i.ItemType == ManagedFileChooserItemType.File && i.DisplayName == initialSelectionName);
if (sel != null)
{
SelectedItems.Add(sel);
}
}
this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
}
catch (System.UnauthorizedAccessException)
{
}
}
}
public void GoUp()
{
var parent = Path.GetDirectoryName(Location);
if (string.IsNullOrWhiteSpace(parent))
{
return;
}
Navigate(parent);
}
public void Cancel()
{
if (!_alreadyCancelled)
{
// INFO: Don't misplace this check or it might cause
// StackOverflowException because of recursive
// event invokes.
_alreadyCancelled = true;
CancelRequested?.Invoke();
}
}
public void Ok()
{
if (_selectingDirectory)
{
CompleteRequested?.Invoke(new[] { Location });
}
else if (_savingFile)
{
if (!string.IsNullOrWhiteSpace(FileName))
{
if (!Path.HasExtension(FileName) && !string.IsNullOrWhiteSpace(_defaultExtension))
{
FileName = Path.ChangeExtension(FileName, _defaultExtension);
}
CompleteRequested?.Invoke(new[] { Path.Combine(Location, FileName) });
}
}
else
{
CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray());
}
}
public void SelectSingleFile(ManagedFileChooserItemViewModel item)
{
CompleteRequested?.Invoke(new[] { item.Path });
}
}
}