diff --git a/Avalonia.sln b/Avalonia.sln
index cb9669fe6b..bf7710d7e8 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2027
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@@ -197,9 +197,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
index 1208be34b8..3baf83f6b5 100644
--- a/build/ReactiveUI.props
+++ b/build/ReactiveUI.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/Rx.props b/build/Rx.props
index 359ce53a92..d40849686f 100644
--- a/build/Rx.props
+++ b/build/Rx.props
@@ -1,5 +1,5 @@
-
+
diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
index d5c6b1d920..b94b1db8bc 100644
--- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
+++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
false
@@ -13,6 +13,9 @@
+
+
+
diff --git a/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs b/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs
new file mode 100644
index 0000000000..3517f760b4
--- /dev/null
+++ b/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Dialogs.Internal
+{
+ public static class ByteSizeHelper
+ {
+ private static readonly string[] Prefixes =
+ {
+ "B",
+ "KB",
+ "MB",
+ "GB",
+ "TB"
+ };
+
+ public static string ToString(long bytes)
+ {
+ var index = 0;
+ while (bytes >= 1000)
+ {
+ bytes /= 1000;
+ ++index;
+ }
+ return $"{bytes:N} {Prefixes[index]}";
+ }
+ }
+}
diff --git a/src/Avalonia.Dialogs/Internal/ChildFitter.cs b/src/Avalonia.Dialogs/Internal/ChildFitter.cs
index d81dafcd99..6f9baff82f 100644
--- a/src/Avalonia.Dialogs/Internal/ChildFitter.cs
+++ b/src/Avalonia.Dialogs/Internal/ChildFitter.cs
@@ -1,20 +1,21 @@
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
namespace Avalonia.Dialogs.Internal
{
- class ChildFitter : Decorator
- {
- protected override Size MeasureOverride(Size availableSize)
- {
- return new Size(0, 0);
- }
+ class ChildFitter : Decorator
+ {
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ return new Size(0, 0);
+ }
- protected override Size ArrangeOverride(Size finalSize)
- {
- Child.Measure(finalSize);
- base.ArrangeOverride(finalSize);
- return finalSize;
- }
- }
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ Child.Measure(finalSize);
+ base.ArrangeOverride(finalSize);
+ return finalSize;
+ }
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs
new file mode 100644
index 0000000000..6de931c89d
--- /dev/null
+++ b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs
@@ -0,0 +1,26 @@
+using Avalonia.Data.Converters;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace Avalonia.Dialogs.Internal
+{
+ public class FileSizeStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if(value is long size && size > 0)
+ {
+ return ByteSizeHelper.ToString(size);
+ }
+
+ return "";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml
index 51154f1161..cb7f2bef34 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml
@@ -4,71 +4,127 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal"
xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Show hidden files
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Show hidden files
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SelectedItems="{Binding SelectedItems}"
+ ScrollViewer.HorizontalScrollBarVisibility="Disabled">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs
index dd23a90922..7aba87115d 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -9,57 +10,79 @@ using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs.Internal
{
- class ManagedFileChooser : UserControl
- {
- private Control _quickLinksRoot;
- private ListBox _filesView;
-
- public ManagedFileChooser()
- {
- AvaloniaXamlLoader.Load(this);
- AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
- _quickLinksRoot = this.FindControl("QuickLinks");
- _filesView = this.FindControl("Files");
- }
-
- ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel;
-
- private void OnPointerPressed(object sender, PointerPressedEventArgs e)
- {
- var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel;
- if(model == null)
- return;
-
- var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control);
- if (e.ClickCount == 2 || isQuickLink)
- {
- if (model.IsDirectory)
- Model?.Navigate(model.Path);
- else
- Model?.SelectSingleFile(model);
- e.Handled = true;
- }
- }
-
- protected override async void OnDataContextChanged(EventArgs e)
- {
- base.OnDataContextChanged(e);
- var model = (DataContext as ManagedFileChooserViewModel);
- if (model == null)
- return;
- var preselected = model.SelectedItems.FirstOrDefault();
- if(preselected == null)
- return;
-
- //Let everything to settle down and scroll to selected item
- await Task.Delay(100);
- if (preselected != model.SelectedItems.FirstOrDefault())
- return;
-
- // Workaround for ListBox bug, scroll to the previous file
- var indexOfPreselected = model.Items.IndexOf(preselected);
- if (indexOfPreselected > 1)
- _filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]);
- }
- }
+ class ManagedFileChooser : UserControl
+ {
+ private Control _quickLinksRoot;
+ private ListBox _filesView;
+
+ public ManagedFileChooser()
+ {
+ AvaloniaXamlLoader.Load(this);
+ AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
+ _quickLinksRoot = this.FindControl("QuickLinks");
+ _filesView = this.FindControl("Files");
+ }
+
+ ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel;
+
+ private void OnPointerPressed(object sender, PointerPressedEventArgs e)
+ {
+ var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel;
+
+ if (model == null)
+ {
+ return;
+ }
+
+ var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control);
+ if (e.ClickCount == 2 || isQuickLink)
+ {
+ if (model.IsDirectory)
+ {
+ Model?.Navigate(model.Path);
+ }
+ else
+ {
+ Model?.SelectSingleFile(model);
+ }
+
+ e.Handled = true;
+ }
+ }
+
+ protected override async void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+
+ var model = (DataContext as ManagedFileChooserViewModel);
+
+ if (model == null)
+ {
+ return;
+ }
+
+ var preselected = model.SelectedItems.FirstOrDefault();
+
+ if (preselected == null)
+ {
+ return;
+ }
+
+ //Let everything to settle down and scroll to selected item
+ await Task.Delay(100);
+
+ if (preselected != model.SelectedItems.FirstOrDefault())
+ {
+ return;
+ }
+
+ // Workaround for ListBox bug, scroll to the previous file
+ var indexOfPreselected = model.Items.IndexOf(preselected);
+
+ if (indexOfPreselected > 1)
+ {
+ _filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]);
+ }
+ }
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
index 1f561d0cd2..d54945ac60 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
@@ -5,34 +5,46 @@ using Avalonia.Controls;
namespace Avalonia.Dialogs.Internal
{
- class ManagedFileChooserFilterViewModel : InternalViewModelBase
- {
- private readonly string[] _extensions;
- public string Name { get; }
-
- public ManagedFileChooserFilterViewModel(FileDialogFilter filter)
- {
- Name = filter.Name;
- if (filter.Extensions.Contains("*"))
- return;
- _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray();
- }
-
- public ManagedFileChooserFilterViewModel()
- {
- Name = "All files";
- }
-
- public bool Match(string filename)
- {
- if (_extensions == null)
- return true;
- foreach(var ext in _extensions)
- if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase))
- return true;
- return false;
- }
-
- public override string ToString() => Name;
- }
+ class ManagedFileChooserFilterViewModel : InternalViewModelBase
+ {
+ private readonly string[] _extensions;
+ public string Name { get; }
+
+ public ManagedFileChooserFilterViewModel(FileDialogFilter filter)
+ {
+ Name = filter.Name;
+
+ if (filter.Extensions.Contains("*"))
+ {
+ return;
+ }
+
+ _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray();
+ }
+
+ public ManagedFileChooserFilterViewModel()
+ {
+ Name = "All files";
+ }
+
+ public bool Match(string filename)
+ {
+ if (_extensions == null)
+ {
+ return true;
+ }
+
+ foreach (var ext in _extensions)
+ {
+ if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public override string ToString() => Name;
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
index d5f72cdbac..b6535a93d0 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
@@ -1,45 +1,69 @@
+using System;
+
namespace Avalonia.Dialogs.Internal
{
- class ManagedFileChooserItemViewModel : InternalViewModelBase
- {
- private string _displayName;
- private string _path;
- private bool _isDirectory;
-
- public string DisplayName
- {
- get => _displayName;
- set => RaiseAndSetIfChanged(ref _displayName, value);
- }
-
- public string Path
- {
- get => _path;
- set => RaiseAndSetIfChanged(ref _path, value);
- }
-
- public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File";
-
- public bool IsDirectory
- {
- get => _isDirectory;
- set
- {
- if (RaiseAndSetIfChanged(ref _isDirectory, value))
- RaisePropertyChanged(nameof(IconKey));
- }
- }
-
- public ManagedFileChooserItemViewModel()
- {
-
- }
-
- public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item)
- {
- IsDirectory = true;
- Path = item.Path;
- DisplayName = item.DisplayName;
- }
- }
+ class ManagedFileChooserItemViewModel : InternalViewModelBase
+ {
+ private string _displayName;
+ private string _path;
+ private bool _isDirectory;
+ private DateTime _modified;
+ private string _type;
+ private long _size;
+
+ public string DisplayName
+ {
+ get => _displayName;
+ set => this.RaiseAndSetIfChanged(ref _displayName, value);
+ }
+
+ public string Path
+ {
+ get => _path;
+ set => this.RaiseAndSetIfChanged(ref _path, value);
+ }
+
+ public DateTime Modified
+ {
+ get => _modified;
+ set => this.RaiseAndSetIfChanged(ref _modified, value);
+ }
+
+ public string Type
+ {
+ get => _type;
+ set => this.RaiseAndSetIfChanged(ref _type, value);
+ }
+
+ public long Size
+ {
+ get => _size;
+ set => this.RaiseAndSetIfChanged(ref _size, value);
+ }
+
+ public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File";
+
+ public bool IsDirectory
+ {
+ get => _isDirectory;
+ set
+ {
+ if (this.RaiseAndSetIfChanged(ref _isDirectory, value))
+ {
+ this.RaisePropertyChanged(nameof(IconKey));
+ }
+ }
+ }
+
+ public ManagedFileChooserItemViewModel()
+ {
+ }
+
+ public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item)
+ {
+ IsDirectory = true;
+ Path = item.Path;
+ DisplayName = item.DisplayName;
+ }
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs
new file mode 100644
index 0000000000..b04a36692c
--- /dev/null
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs
@@ -0,0 +1,8 @@
+namespace Avalonia.Dialogs.Internal
+{
+ public class ManagedFileChooserNavigationItem
+ {
+ public string DisplayName { get; set; }
+ public string Path { get; set; }
+ }
+}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
index 587b32fe03..84a3173eac 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
@@ -5,71 +5,85 @@ using System.Runtime.InteropServices;
namespace Avalonia.Dialogs.Internal
{
- public class ManagedFileChooserSources
- {
- public Func GetUserDirectories { get; set; }
- = DefaultGetUserDirectories;
+ public class ManagedFileChooserSources
+ {
+ public Func GetUserDirectories { get; set; }
+ = DefaultGetUserDirectories;
- public Func GetFileSystemRoots { get; set; }
- = DefaultGetFileSystemRoots;
+ public Func GetFileSystemRoots { get; set; }
+ = DefaultGetFileSystemRoots;
- public Func GetAllItemsDelegate { get; set; }
- = DefaultGetAllItems;
+ public Func GetAllItemsDelegate { get; set; }
+ = DefaultGetAllItems;
- public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this);
+ public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this);
- public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources)
- {
- return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray();
- }
-
- private static Environment.SpecialFolder[] s_folders = new[]
- {
- Environment.SpecialFolder.Desktop,
- Environment.SpecialFolder.UserProfile,
- Environment.SpecialFolder.MyDocuments,
- Environment.SpecialFolder.MyMusic,
- Environment.SpecialFolder.MyPictures,
- Environment.SpecialFolder.MyVideos
- };
-
- public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories()
- {
- return s_folders.Select(Environment.GetFolderPath).Distinct()
- .Where(d => !string.IsNullOrWhiteSpace(d))
- .Where(Directory.Exists)
- .Select(d => new ManagedFileChooserNavigationItem
- {
- Path = d,
- DisplayName = Path.GetFileName(d)
- }).ToArray();
- }
+ public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources)
+ {
+ return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray();
+ }
- public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem
- {
- DisplayName = d.Name,
- Path = d.RootDirectory.FullName
- }).ToArray();
- }
+ private static Environment.SpecialFolder[] s_folders = new[]
+ {
+ Environment.SpecialFolder.Desktop,
+ Environment.SpecialFolder.UserProfile,
+ Environment.SpecialFolder.MyDocuments,
+ Environment.SpecialFolder.MyMusic,
+ Environment.SpecialFolder.MyPictures,
+ Environment.SpecialFolder.MyVideos
+ };
- return new[]
- {
- new ManagedFileChooserNavigationItem
- {
- DisplayName = "File System",
- Path = "/"
- }
- };
- }
- }
+ public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories()
+ {
+ return s_folders.Select(Environment.GetFolderPath).Distinct()
+ .Where(d => !string.IsNullOrWhiteSpace(d))
+ .Where(Directory.Exists)
+ .Select(d => new ManagedFileChooserNavigationItem
+ {
+ Path = d,
+ DisplayName = Path.GetFileName(d)
+ }).ToArray();
+ }
- public class ManagedFileChooserNavigationItem
- {
- public string DisplayName { get; set; }
- public string Path { get; set; }
- }
+ public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem
+ {
+ DisplayName = d.Name,
+ Path = d.RootDirectory.FullName
+ }).ToArray();
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ var paths = Directory.GetDirectories("/Volumes");
+
+ return paths.Select(x => new ManagedFileChooserNavigationItem
+ {
+ DisplayName = Path.GetFileName(x),
+ Path = x
+ }).ToArray();
+ }
+ else
+ {
+ var paths = Directory.GetDirectories("/media/");
+
+ var drives = new ManagedFileChooserNavigationItem[]
+ {
+ new ManagedFileChooserNavigationItem
+ {
+ DisplayName = "File System",
+ Path = "/"
+ }
+ }.Concat(paths.Select(x => new ManagedFileChooserNavigationItem
+ {
+ DisplayName = Path.GetFileName(x),
+ Path = x
+ })).ToArray();
+
+ return drives;
+ }
+ }
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
index 72bff302bd..8f557a212d 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
@@ -2,222 +2,334 @@ using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
+using System.Reactive;
using System.Runtime.InteropServices;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
+using ReactiveUI;
namespace Avalonia.Dialogs.Internal
{
class ManagedFileChooserViewModel : InternalViewModelBase
- {
- public event Action CancelRequested;
- public event Action CompleteRequested;
-
- public AvaloniaList QuickLinks { get; } =
- new AvaloniaList();
-
- public AvaloniaList Items { get; } =
- new AvaloniaList();
-
- public AvaloniaList Filters { get; } =
- new AvaloniaList();
-
- public AvaloniaList SelectedItems { get; } =
- new AvaloniaList();
-
- string _location;
- private bool _showHiddenFiles;
- private ManagedFileChooserFilterViewModel _selectedFilter;
- private bool _selectingDirectory;
- private bool _scheduledSelectionValidation;
-
- public string Location
- {
- get => _location;
- private set => RaiseAndSetIfChanged(ref _location, value);
- }
-
- 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 => RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
- }
-
- public ManagedFileChooserFilterViewModel SelectedFilter
- {
- get => _selectedFilter;
- set
- {
- RaiseAndSetIfChanged(ref _selectedFilter, value);
- Refresh();
- }
- }
-
- public bool ShowHiddenFiles
- {
- get => _showHiddenFiles;
- set
- {
- RaiseAndSetIfChanged(ref _showHiddenFiles, value);
- Refresh();
- }
- }
-
- public ManagedFileChooserViewModel(FileSystemDialog dialog)
- {
- var quickSources = AvaloniaLocator.Current.GetService()
- ?? new ManagedFileChooserSources();
- QuickLinks.Clear();
-
- QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
- 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;
-
- Navigate(directory, (dialog as FileDialog)?.InitialFileName);
- SelectedItems.CollectionChanged += OnSelectionChanged;
- }
-
- private async void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- if(_scheduledSelectionValidation)
- return;
- _scheduledSelectionValidation = true;
- await Dispatcher.UIThread.InvokeAsync(() =>
- {
- try
- {
- if(_selectingDirectory)
- SelectedItems.Clear();
- else
- {
- var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList();
- foreach (var item in invalidItems)
- SelectedItems.Remove(item);
- }
- }
- 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();
- 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.Select(info => new ManagedFileChooserItemViewModel
- {
- DisplayName = info.Name,
- Path = info.FullName,
- IsDirectory = info is DirectoryInfo
- }).OrderByDescending(x => x.IsDirectory)
- .ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase));
-
- if (initialSelectionName != null)
- {
- var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName);
- if (sel != null)
- SelectedItems.Add(sel);
- }
-
- RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
- }
-
-
- }
-
- public void GoUp()
- {
- var parent = Path.GetDirectoryName(Location);
- if (string.IsNullOrWhiteSpace(parent))
- return;
- Navigate(parent);
- }
-
- public void Cancel()
- {
- CancelRequested?.Invoke();
- }
-
- public void Ok()
- {
- if (_selectingDirectory)
- CompleteRequested?.Invoke(new[] {Location});
- else
- CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray());
- }
-
- public void SelectSingleFile(ManagedFileChooserItemViewModel item)
- {
- CompleteRequested?.Invoke(new[] {item.Path});
- }
- }
+ {
+ public event Action CancelRequested;
+ public event Action CompleteRequested;
+
+ public AvaloniaList QuickLinks { get; } =
+ new AvaloniaList();
+
+ public AvaloniaList Items { get; } =
+ new AvaloniaList();
+
+ public AvaloniaList Filters { get; } =
+ new AvaloniaList();
+
+ public AvaloniaList SelectedItems { get; } =
+ new AvaloniaList();
+
+ string _location;
+ string _fileName;
+ private bool _showHiddenFiles;
+ private ManagedFileChooserFilterViewModel _selectedFilter;
+ private bool _selectingDirectory;
+ private bool _savingFile;
+ private bool _scheduledSelectionValidation;
+ private string _defaultExtension;
+
+ 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();
+ }
+ }
+
+ public ManagedFileChooserViewModel(FileSystemDialog dialog)
+ {
+ var quickSources = AvaloniaLocator.Current.GetService()
+ ?? new ManagedFileChooserSources();
+
+ QuickLinks.Clear();
+
+ QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
+
+ 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.InitialDirectory;
+
+ 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;
+
+ EnterLocationCommand = ReactiveCommand.Create(() =>
+ {
+ 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
+ {
+ var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList();
+ foreach (var item in invalidItems)
+ {
+ SelectedItems.Remove(item);
+ }
+
+ if(!_selectingDirectory)
+ {
+ FileName = SelectedItems.FirstOrDefault()?.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;
+ }).Select(info => new ManagedFileChooserItemViewModel
+ {
+ DisplayName = info.Name,
+ Path = info.FullName,
+ IsDirectory = info is DirectoryInfo,
+ Type = info is FileInfo ? info.Extension : "File Folder",
+ Size = info is FileInfo f ? f.Length : 0,
+ Modified = info.LastWriteTime
+ })
+ .OrderByDescending(x => x.IsDirectory)
+ .ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase));
+
+ if (initialSelectionName != null)
+ {
+ var sel = Items.FirstOrDefault(i => !i.IsDirectory && 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()
+ {
+ 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 });
+ }
+
+ public ReactiveCommand EnterLocationCommand { get; }
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml
index 34c8dba363..002f35ae24 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml
@@ -2,6 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal"
xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal"
- Title="{Binding Title}" MinWidth="700" MinHeight="500">
-
+ Icon="resm:WalletWasabi.Gui.Assets.WasabiLogo256.png?assembly=WalletWasabi.Gui"
+ Title="{Binding Title}" Width="1100" Height="500" MinWidth="1100" MinHeight="500"
+ FontFamily="{DynamicResource UiFont}" FontSize="14"
+ Foreground="{DynamicResource ThemeForegroundBrush}">
+
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs
index 43276f5e90..34b0163808 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs
@@ -1,18 +1,20 @@
using System;
+using System.Runtime.InteropServices;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Media;
namespace Avalonia.Dialogs.Internal
{
- class ManagedFileDialog : Window
- {
- private ManagedFileChooserViewModel _model;
- public ManagedFileDialog()
- {
- AvaloniaXamlLoader.Load(this);
- #if DEBUG
- this.AttachDevTools();
- #endif
- }
- }
+ class ManagedFileDialog : Window
+ {
+ public ManagedFileDialog()
+ {
+ AvaloniaXamlLoader.Load(this);
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+ }
}
diff --git a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
index a492dfed3a..8346ba952c 100644
--- a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
+++ b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
@@ -5,17 +5,17 @@ using Avalonia.Data.Converters;
namespace Avalonia.Dialogs.Internal
{
- public class ResourceSelectorConverter : ResourceDictionary, IValueConverter
- {
- public object Convert(object key, Type targetType, object parameter, CultureInfo culture)
- {
- TryGetResource((string)key, out var value);
- return value;
- }
+ public class ResourceSelectorConverter : ResourceDictionary, IValueConverter
+ {
+ public object Convert(object key, Type targetType, object parameter, CultureInfo culture)
+ {
+ TryGetResource((string)key, out var value);
+ return value;
+ }
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
index 26a11fc4ff..8c0aba5942 100644
--- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
+++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Dialogs.Internal;
@@ -7,48 +8,48 @@ using Avalonia.Platform;
namespace Avalonia.Dialogs
{
- public static class ManagedFileDialogExtensions
- {
- class ManagedSystemDialogImpl : ISystemDialogImpl
- {
- async Task Show(SystemDialog d, IWindowImpl parent)
- {
- var model = new ManagedFileChooserViewModel((FileSystemDialog)d);
-
- var dialog = new ManagedFileDialog
- {
- DataContext = model
- };
+ public static class ManagedFileDialogExtensions
+ {
+ class ManagedSystemDialogImpl : ISystemDialogImpl
+ {
+ async Task Show(SystemDialog d, IWindowImpl parent)
+ {
+ var model = new ManagedFileChooserViewModel((FileSystemDialog)d);
- string[] result = null;
- model.CompleteRequested += items =>
- {
- result = items;
- dialog.Close();
- };
- model.CancelRequested += dialog.Close;
+ var dialog = new ManagedFileDialog
+ {
+ DataContext = model
+ };
- await dialog.ShowDialog