Browse Source

Update macOS implementations with some UTType support

pull/8303/head
Max Katz 4 years ago
parent
commit
06e141bf81
  1. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  2. 52
      native/Avalonia.Native/src/OSX/SystemDialogs.mm
  3. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  4. 118
      src/Avalonia.Native/SystemDialogs.cs
  5. 7
      src/Avalonia.Native/WindowImplBase.cs
  6. 7
      src/Avalonia.Native/avn.idl

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -49,6 +49,7 @@
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -101,6 +102,7 @@
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -108,6 +110,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */,
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */,
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */,
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
@ -122,6 +125,7 @@
AB661C1C2148230E00291242 /* Frameworks */ = {
isa = PBXGroup;
children = (
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */,
522D5958258159C1006F7F7A /* Carbon.framework */,
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */,
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,

52
native/Avalonia.Native/src/OSX/SystemDialogs.mm

@ -1,5 +1,6 @@
#include "common.h"
#include "INSWindowHolder.h"
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
{
@ -7,6 +8,7 @@ public:
FORWARD_IUNKNOWN()
virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,
bool allowMultiple,
const char* title,
const char* initialDirectory) override
{
@ -14,6 +16,7 @@ public:
{
auto panel = [NSOpenPanel openPanel];
panel.allowsMultipleSelection = allowMultiple;
panel.canChooseDirectories = true;
panel.canCreateDirectories = true;
panel.canChooseFiles = false;
@ -118,7 +121,15 @@ public:
{
auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
panel.allowedFileTypes = allowedTypes;
// Prefer allowedContentTypes if available
if (@available(macOS 11.0, *))
{
panel.allowedContentTypes = ConvertToUTType(allowedTypes);
}
else
{
panel.allowedFileTypes = allowedTypes;
}
}
}
@ -207,7 +218,18 @@ public:
{
auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
panel.allowedFileTypes = allowedTypes;
// Prefer allowedContentTypes if available
if (@available(macOS 11.0, *))
{
panel.allowedContentTypes = ConvertToUTType(allowedTypes);
}
else
{
panel.allowedFileTypes = allowedTypes;
}
panel.allowsOtherFileTypes = false;
panel.extensionHidden = false;
}
}
@ -250,6 +272,32 @@ public:
}
}
}
private:
NSMutableArray* ConvertToUTType(NSArray<NSString*>* allowedTypes)
{
auto originalCount = [allowedTypes count];
auto mapped = [[NSMutableArray alloc] init];
if (@available(macOS 11.0, *))
{
for (int i = 0; i < originalCount; i++)
{
auto utTypeStr = allowedTypes[i];
auto utType = [UTType typeWithIdentifier:utTypeStr];
if (utType == nil)
{
utType = [UTType typeWithMIMEType:utTypeStr];
}
if (utType != nil)
{
[mapped addObject:utType];
}
}
}
return mapped;
}
};

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -112,7 +112,6 @@ namespace Avalonia.Native
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))

118
src/Avalonia.Native/SystemDialogs.cs

@ -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++;
}

7
src/Avalonia.Native/WindowImplBase.cs

@ -11,6 +11,7 @@ using Avalonia.Input.Raw;
using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Threading;
@ -45,7 +46,7 @@ namespace Avalonia.Native
}
internal abstract class WindowBaseImpl : IWindowBaseImpl,
IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost
IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
protected readonly IAvaloniaNativeFactory _factory;
protected IInputRoot _inputRoot;
@ -73,6 +74,7 @@ namespace Avalonia.Native
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
StorageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
}
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
@ -84,6 +86,7 @@ namespace Avalonia.Native
if (_gpu)
_glSurface = new GlPlatformSurface(window, _glContext);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
@ -514,5 +517,7 @@ namespace Avalonia.Native
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
public IPlatformHandle Handle { get; private set; }
public IStorageProvider StorageProvider { get; }
}
}

7
src/Avalonia.Native/avn.idl

@ -641,9 +641,10 @@ interface IAvnSystemDialogEvents : IUnknown
interface IAvnSystemDialogs : IUnknown
{
void SelectFolderDialog(IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,
[const] char* title,
[const] char* initialPath);
IAvnSystemDialogEvents* events,
bool allowMultiple,
[const] char* title,
[const] char* initialPath);
void OpenFileDialog(IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,

Loading…
Cancel
Save