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

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

@ -1,5 +1,6 @@
#include "common.h" #include "common.h"
#include "INSWindowHolder.h" #include "INSWindowHolder.h"
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs> class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
{ {
@ -7,6 +8,7 @@ public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle, virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events, IAvnSystemDialogEvents* events,
bool allowMultiple,
const char* title, const char* title,
const char* initialDirectory) override const char* initialDirectory) override
{ {
@ -14,6 +16,7 @@ public:
{ {
auto panel = [NSOpenPanel openPanel]; auto panel = [NSOpenPanel openPanel];
panel.allowsMultipleSelection = allowMultiple;
panel.canChooseDirectories = true; panel.canChooseDirectories = true;
panel.canCreateDirectories = true; panel.canCreateDirectories = true;
panel.canChooseFiles = false; panel.canChooseFiles = false;
@ -118,7 +121,15 @@ public:
{ {
auto allowedTypes = [filtersString componentsSeparatedByString:@";"]; 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:@";"]; 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<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) .Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt)) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)) .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.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.Native 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; _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((IAvnWindow)_window.Native,
{ events,
_native.OpenFileDialog(nativeParent, options.AllowMultiple.AsComBool(),
events, ofd.AllowMultiple.AsComBool(), options.Title ?? string.Empty,
ofd.Title ?? "", suggestedDirectory,
ofd.Directory ?? "", string.Empty,
ofd.InitialFileName ?? "", PrepareFilterParameter(options.FileTypeFilter));
string.Join(";", dialog.Filters?.SelectMany(f => f.Extensions) ?? Array.Empty<string>()));
} var result = await events.Task.ConfigureAwait(false);
else
{ return result?.Select(f => new BclStorageFile(new FileInfo(f))).ToArray()
_native.SaveFileDialog(nativeParent, ?? Array.Empty<IStorageFile>();
events, }
dialog.Title ?? "",
dialog.Directory ?? "",
dialog.InitialFileName ?? "",
string.Join(";", dialog.Filters?.SelectMany(f => f.Extensions) ?? Array.Empty<string>()));
}
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 internal unsafe class SystemDialogEvents : NativeCallbackBase, IAvnSystemDialogEvents
{ {
private TaskCompletionSource<string[]> _tcs; private readonly TaskCompletionSource<string[]> _tcs;
public SystemDialogEvents() public SystemDialogEvents()
{ {
@ -83,7 +127,7 @@ namespace Avalonia.Native
for (int i = 0; i < numResults; i++) for (int i = 0; i < numResults; i++)
{ {
results[i] = Marshal.PtrToStringAnsi(*ptr); results[i] = Marshal.PtrToStringAnsi(*ptr) ?? string.Empty;
ptr++; ptr++;
} }

7
src/Avalonia.Native/WindowImplBase.cs

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

Loading…
Cancel
Save