|
|
|
@ -1,70 +1,114 @@ |
|
|
|
using System; |
|
|
|
#nullable enable |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.IO; |
|
|
|
using System.Linq; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using Avalonia.Controls; |
|
|
|
using Avalonia.Controls.Platform; |
|
|
|
using Avalonia.Native.Interop; |
|
|
|
using Avalonia.Platform.Storage; |
|
|
|
using Avalonia.Platform.Storage.FileIO; |
|
|
|
|
|
|
|
namespace Avalonia.Native |
|
|
|
{ |
|
|
|
internal class SystemDialogs : ISystemDialogImpl |
|
|
|
internal class SystemDialogs : BclStorageProvider |
|
|
|
{ |
|
|
|
IAvnSystemDialogs _native; |
|
|
|
private readonly WindowBaseImpl _window; |
|
|
|
private readonly IAvnSystemDialogs _native; |
|
|
|
|
|
|
|
public SystemDialogs(IAvnSystemDialogs native) |
|
|
|
public SystemDialogs(WindowBaseImpl window, IAvnSystemDialogs native) |
|
|
|
{ |
|
|
|
_window = window; |
|
|
|
_native = native; |
|
|
|
} |
|
|
|
|
|
|
|
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent) |
|
|
|
public override bool CanOpen => true; |
|
|
|
|
|
|
|
public override bool CanSave => true; |
|
|
|
|
|
|
|
public override bool CanPickFolder => true; |
|
|
|
|
|
|
|
public override async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options) |
|
|
|
{ |
|
|
|
var events = new SystemDialogEvents(); |
|
|
|
using var events = new SystemDialogEvents(); |
|
|
|
|
|
|
|
var nativeParent = GetNativeWindow(parent); |
|
|
|
var suggestedDirectory = options.SuggestedStartLocation?.TryGetUri(out var suggestedDirectoryTmp) == true |
|
|
|
? suggestedDirectoryTmp.LocalPath : string.Empty; |
|
|
|
|
|
|
|
if (dialog is OpenFileDialog ofd) |
|
|
|
{ |
|
|
|
_native.OpenFileDialog(nativeParent, |
|
|
|
events, ofd.AllowMultiple.AsComBool(), |
|
|
|
ofd.Title ?? "", |
|
|
|
ofd.Directory ?? "", |
|
|
|
ofd.InitialFileName ?? "", |
|
|
|
string.Join(";", dialog.Filters?.SelectMany(f => f.Extensions) ?? Array.Empty<string>())); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
_native.SaveFileDialog(nativeParent, |
|
|
|
events, |
|
|
|
dialog.Title ?? "", |
|
|
|
dialog.Directory ?? "", |
|
|
|
dialog.InitialFileName ?? "", |
|
|
|
string.Join(";", dialog.Filters?.SelectMany(f => f.Extensions) ?? Array.Empty<string>())); |
|
|
|
} |
|
|
|
_native.OpenFileDialog((IAvnWindow)_window.Native, |
|
|
|
events, |
|
|
|
options.AllowMultiple.AsComBool(), |
|
|
|
options.Title ?? string.Empty, |
|
|
|
suggestedDirectory, |
|
|
|
string.Empty, |
|
|
|
PrepareFilterParameter(options.FileTypeFilter)); |
|
|
|
|
|
|
|
var result = await events.Task.ConfigureAwait(false); |
|
|
|
|
|
|
|
return result?.Select(f => new BclStorageFile(new FileInfo(f))).ToArray() |
|
|
|
?? Array.Empty<IStorageFile>(); |
|
|
|
} |
|
|
|
|
|
|
|
return events.Task.ContinueWith(t => { events.Dispose(); return t.Result; }); |
|
|
|
public override async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options) |
|
|
|
{ |
|
|
|
using var events = new SystemDialogEvents(); |
|
|
|
|
|
|
|
var suggestedDirectory = options.SuggestedStartLocation?.TryGetUri(out var suggestedDirectoryTmp) == true |
|
|
|
? suggestedDirectoryTmp.LocalPath : string.Empty; |
|
|
|
|
|
|
|
_native.SaveFileDialog((IAvnWindow)_window.Native, |
|
|
|
events, |
|
|
|
options.Title ?? string.Empty, |
|
|
|
suggestedDirectory, |
|
|
|
options.SuggestedFileName ?? string.Empty, |
|
|
|
PrepareFilterParameter(options.FileTypeChoices)); |
|
|
|
|
|
|
|
var result = await events.Task.ConfigureAwait(false); |
|
|
|
return result.FirstOrDefault() is string file |
|
|
|
? new BclStorageFile(new FileInfo(file)) |
|
|
|
: null; |
|
|
|
} |
|
|
|
|
|
|
|
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) |
|
|
|
public override async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options) |
|
|
|
{ |
|
|
|
var events = new SystemDialogEvents(); |
|
|
|
using var events = new SystemDialogEvents(); |
|
|
|
|
|
|
|
var nativeParent = GetNativeWindow(parent); |
|
|
|
var suggestedDirectory = options.SuggestedStartLocation?.TryGetUri(out var suggestedDirectoryTmp) == true |
|
|
|
? suggestedDirectoryTmp.LocalPath : string.Empty; |
|
|
|
|
|
|
|
_native.SelectFolderDialog(nativeParent, events, dialog.Title ?? "", dialog.Directory ?? ""); |
|
|
|
_native.SelectFolderDialog((IAvnWindow)_window.Native, events, options.AllowMultiple.AsComBool(), options.Title ?? "", suggestedDirectory); |
|
|
|
|
|
|
|
return events.Task.ContinueWith(t => { events.Dispose(); return t.Result.FirstOrDefault(); }); |
|
|
|
var result = await events.Task.ConfigureAwait(false); |
|
|
|
return result?.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray() |
|
|
|
?? Array.Empty<IStorageFolder>(); |
|
|
|
} |
|
|
|
|
|
|
|
private IAvnWindow GetNativeWindow(Window window) |
|
|
|
private static string PrepareFilterParameter(IReadOnlyList<FilePickerFileType>? fileTypes) |
|
|
|
{ |
|
|
|
return (window?.PlatformImpl as WindowImpl)?.Native; |
|
|
|
return string.Join(";", |
|
|
|
fileTypes?.SelectMany(f => |
|
|
|
{ |
|
|
|
// On the native side we will try to parse identifiers or mimetypes.
|
|
|
|
if (f.AppleUniformTypeIdentifiers?.Any() == true) |
|
|
|
{ |
|
|
|
return f.AppleUniformTypeIdentifiers; |
|
|
|
} |
|
|
|
else if (f.MimeTypes?.Any() == true) |
|
|
|
{ |
|
|
|
// MacOS doesn't accept "all" type, so it's pointless to pass it.
|
|
|
|
return f.MimeTypes.Where(t => t != "*/*"); |
|
|
|
} |
|
|
|
|
|
|
|
return Array.Empty<string>(); |
|
|
|
}) ?? |
|
|
|
Array.Empty<string>()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
internal unsafe class SystemDialogEvents : NativeCallbackBase, IAvnSystemDialogEvents |
|
|
|
{ |
|
|
|
private TaskCompletionSource<string[]> _tcs; |
|
|
|
private readonly TaskCompletionSource<string[]> _tcs; |
|
|
|
|
|
|
|
public SystemDialogEvents() |
|
|
|
{ |
|
|
|
@ -83,7 +127,7 @@ namespace Avalonia.Native |
|
|
|
|
|
|
|
for (int i = 0; i < numResults; i++) |
|
|
|
{ |
|
|
|
results[i] = Marshal.PtrToStringAnsi(*ptr); |
|
|
|
results[i] = Marshal.PtrToStringAnsi(*ptr) ?? string.Empty; |
|
|
|
|
|
|
|
ptr++; |
|
|
|
} |
|
|
|
|