Browse Source

Merge branch 'features/updatable-native-app-menu' of https://github.com/AvaloniaUI/Avalonia into features/updatable-native-app-menu

pull/3762/head
Dan Walmsley 7 years ago
parent
commit
7276c2be00
  1. 5
      src/Avalonia.Controls/Platform/ISystemDialogImpl.cs
  2. 6
      src/Avalonia.Controls/SystemDialog.cs
  3. 28
      src/Avalonia.Controls/Window.cs
  4. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  5. 12
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
  6. 20
      src/Avalonia.Native/SystemDialogs.cs
  7. 14
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  8. 17
      src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs
  9. 9
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  10. 8
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  11. 44
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs

5
src/Avalonia.Controls/Platform/ISystemDialogImpl.cs

@ -1,5 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
@ -14,8 +13,8 @@ namespace Avalonia.Controls.Platform
/// <param name="dialog">The details of the file dialog to show.</param> /// <param name="dialog">The details of the file dialog to show.</param>
/// <param name="parent">The parent window.</param> /// <param name="parent">The parent window.</param>
/// <returns>A task returning the selected filenames.</returns> /// <returns>A task returning the selected filenames.</returns>
Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent);
Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent); Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent);
} }
} }

6
src/Avalonia.Controls/SystemDialog.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls
if(parent == null) if(parent == null)
throw new ArgumentNullException(nameof(parent)); throw new ArgumentNullException(nameof(parent));
return ((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>() return ((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>()
.ShowFileDialogAsync(this, parent?.PlatformImpl)) ?? .ShowFileDialogAsync(this, parent)) ??
Array.Empty<string>()).FirstOrDefault(); Array.Empty<string>()).FirstOrDefault();
} }
} }
@ -45,7 +45,7 @@ namespace Avalonia.Controls
{ {
if(parent == null) if(parent == null)
throw new ArgumentNullException(nameof(parent)); throw new ArgumentNullException(nameof(parent));
return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, parent?.PlatformImpl); return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, parent);
} }
} }
@ -61,7 +61,7 @@ namespace Avalonia.Controls
{ {
if(parent == null) if(parent == null)
throw new ArgumentNullException(nameof(parent)); throw new ArgumentNullException(nameof(parent));
return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, parent?.PlatformImpl); return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, parent);
} }
} }

28
src/Avalonia.Controls/Window.cs

@ -484,22 +484,12 @@ namespace Avalonia.Controls
/// <returns>. /// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes. /// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns> /// </returns>
public Task<TResult> ShowDialog<TResult>(Window owner) => ShowDialog<TResult>(owner.PlatformImpl); public Task<TResult> ShowDialog<TResult>(Window owner)
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by the dialog.
/// </typeparam>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>(IWindowImpl owner)
{ {
if (owner == null) if (owner == null)
{
throw new ArgumentNullException(nameof(owner)); throw new ArgumentNullException(nameof(owner));
}
if (IsVisible) if (IsVisible)
{ {
@ -516,23 +506,25 @@ namespace Avalonia.Controls
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
PlatformImpl?.ShowDialog(owner.PlatformImpl);
PlatformImpl?.ShowDialog(owner);
Renderer?.Start(); Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>( Observable.FromEventPattern<EventHandler, EventArgs>(
x => this.Closed += x, x => Closed += x,
x => this.Closed -= x) x => Closed -= x)
.Take(1) .Take(1)
.Subscribe(_ => .Subscribe(_ =>
{ {
owner.Activate(); owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult))); result.SetResult((TResult)(_dialogResult ?? default(TResult)));
}); });
OnOpened(EventArgs.Empty); OnOpened(EventArgs.Empty);
} }
SetWindowStartupLocation(owner); SetWindowStartupLocation(owner.PlatformImpl);
return result.Task; return result.Task;
} }

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -166,10 +166,10 @@ namespace Avalonia.DesignerSupport.Remote
class SystemDialogsStub : ISystemDialogImpl class SystemDialogsStub : ISystemDialogImpl
{ {
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent) =>
Task.FromResult((string[])null); Task.FromResult((string[])null);
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) =>
Task.FromResult((string)null); Task.FromResult((string)null);
} }

12
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -1,19 +1,15 @@
using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Dialogs;
using Avalonia.Platform;
namespace Avalonia.Dialogs namespace Avalonia.Dialogs
{ {
public static class ManagedFileDialogExtensions public static class ManagedFileDialogExtensions
{ {
class ManagedSystemDialogImpl<T> : ISystemDialogImpl where T : Window, new() private class ManagedSystemDialogImpl<T> : ISystemDialogImpl where T : Window, new()
{ {
async Task<string[]> Show(SystemDialog d, IWindowImpl parent) async Task<string[]> Show(SystemDialog d, Window parent)
{ {
var model = new ManagedFileChooserViewModel((FileSystemDialog)d); var model = new ManagedFileChooserViewModel((FileSystemDialog)d);
@ -39,12 +35,12 @@ namespace Avalonia.Dialogs
return result; return result;
} }
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{ {
return await Show(dialog, parent); return await Show(dialog, parent);
} }
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{ {
return (await Show(dialog, parent))?.FirstOrDefault(); return (await Show(dialog, parent))?.FirstOrDefault();
} }

20
src/Avalonia.Native/SystemDialogs.cs

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
@ -18,13 +17,15 @@ namespace Avalonia.Native
_native = native; _native = native;
} }
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{ {
var events = new SystemDialogEvents(); var events = new SystemDialogEvents();
var nativeParent = GetNativeWindow(parent);
if (dialog is OpenFileDialog ofd) if (dialog is OpenFileDialog ofd)
{ {
_native.OpenFileDialog((parent as WindowImpl)?.Native, _native.OpenFileDialog(nativeParent,
events, ofd.AllowMultiple, events, ofd.AllowMultiple,
ofd.Title ?? "", ofd.Title ?? "",
ofd.InitialDirectory ?? "", ofd.InitialDirectory ?? "",
@ -33,7 +34,7 @@ namespace Avalonia.Native
} }
else else
{ {
_native.SaveFileDialog((parent as WindowImpl)?.Native, _native.SaveFileDialog(nativeParent,
events, events,
dialog.Title ?? "", dialog.Title ?? "",
dialog.InitialDirectory ?? "", dialog.InitialDirectory ?? "",
@ -44,14 +45,21 @@ namespace Avalonia.Native
return events.Task.ContinueWith(t => { events.Dispose(); return t.Result; }); return events.Task.ContinueWith(t => { events.Dispose(); return t.Result; });
} }
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{ {
var events = new SystemDialogEvents(); var events = new SystemDialogEvents();
_native.SelectFolderDialog((parent as WindowImpl)?.Native, events, dialog.Title ?? "", dialog.InitialDirectory ?? ""); var nativeParent = GetNativeWindow(parent);
_native.SelectFolderDialog(nativeParent, events, dialog.Title ?? "", dialog.InitialDirectory ?? "");
return events.Task.ContinueWith(t => { events.Dispose(); return t.Result.FirstOrDefault(); }); return events.Task.ContinueWith(t => { events.Dispose(); return t.Result.FirstOrDefault(); });
} }
private IAvnWindow GetNativeWindow(Window window)
{
return (window?.PlatformImpl as WindowImpl)?.Native;
}
} }
public class SystemDialogEvents : CallbackBase, IAvnSystemDialogEvents public class SystemDialogEvents : CallbackBase, IAvnSystemDialogEvents

14
src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs

@ -102,23 +102,29 @@ namespace Avalonia.X11.NativeDialogs
return tcs.Task; return tcs.Task;
} }
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{ {
await EnsureInitialized(); await EnsureInitialized();
var platformImpl = parent?.PlatformImpl;
return await await RunOnGlibThread( return await await RunOnGlibThread(
() => ShowDialog(dialog.Title, parent, () => ShowDialog(dialog.Title, platformImpl,
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false, (dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory, Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters)); string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters));
} }
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{ {
await EnsureInitialized(); await EnsureInitialized();
var platformImpl = parent?.PlatformImpl;
return await await RunOnGlibThread(async () => return await await RunOnGlibThread(async () =>
{ {
var res = await ShowDialog(dialog.Title, parent, var res = await ShowDialog(dialog.Title, platformImpl,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, null); GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, null);
return res?.FirstOrDefault(); return res?.FirstOrDefault();
}); });

17
src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs

@ -4,7 +4,8 @@ using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Utilities;
public class PointsListTypeConverter : TypeConverter public class PointsListTypeConverter : TypeConverter
{ {
@ -15,15 +16,17 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
string strValue = (string)value; var points = new List<Point>();
string[] pointStrs = strValue.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var result = new List<Point>(pointStrs.Length); using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PointsList."))
foreach (var pointStr in pointStrs)
{ {
result.Add(Point.Parse(pointStr)); while (tokenizer.TryReadDouble(out double x))
{
points.Add(new Point(x, tokenizer.ReadDouble()));
}
} }
return result; return points;
} }
} }
} }

9
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -5,7 +5,6 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
namespace Avalonia.Win32 namespace Avalonia.Win32
@ -16,9 +15,9 @@ namespace Avalonia.Win32
private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE |
UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT; UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT;
public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{ {
var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero;
return Task.Factory.StartNew(() => return Task.Factory.StartNew(() =>
{ {
var result = Array.Empty<string>(); var result = Array.Empty<string>();
@ -98,13 +97,13 @@ namespace Avalonia.Win32
}); });
} }
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{ {
return Task.Factory.StartNew(() => return Task.Factory.StartNew(() =>
{ {
string result = string.Empty; string result = string.Empty;
var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero;
Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog; Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog;
Guid iid = UnmanagedMethods.ShellIds.IFileDialog; Guid iid = UnmanagedMethods.ShellIds.IFileDialog;

8
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -156,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = Mock.Of<IWindowImpl>(); var parent = Mock.Of<Window>();
var renderer = new Mock<IRenderer>(); var renderer = new Mock<IRenderer>();
var target = new Window(CreateImpl(renderer)); var target = new Window(CreateImpl(renderer));
@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = Mock.Of<IWindowImpl>(); var parent = Mock.Of<Window>();
var target = new Window(); var target = new Window();
var raised = false; var raised = false;
@ -203,7 +203,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Mock<IWindowImpl>(); var parent = new Mock<Window>();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1); windowImpl.Setup(x => x.Scaling).Returns(1);
@ -242,7 +242,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Mock<IWindowImpl>(); var parent = new Mock<Window>();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1); windowImpl.Setup(x => x.Scaling).Returns(1);

44
tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs

@ -0,0 +1,44 @@
using System.Collections.Generic;
using Avalonia.Controls.Shapes;
using Avalonia.Markup.Xaml.Converters;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
public class PointsListTypeConverterTests
{
[Theory]
[InlineData("1,2 3,4")]
[InlineData("1 2 3 4")]
[InlineData("1 2,3 4")]
[InlineData("1,2,3,4")]
public void TypeConverter_Should_Parse(string input)
{
var conv = new PointsListTypeConverter();
var points = (IList<Point>)conv.ConvertFrom(input);
Assert.Equal(2, points.Count);
Assert.Equal(new Point(1, 2), points[0]);
Assert.Equal(new Point(3, 4), points[1]);
}
[Theory]
[InlineData("1,2 3,4")]
[InlineData("1 2 3 4")]
[InlineData("1 2,3 4")]
[InlineData("1,2,3,4")]
public void Should_Parse_Points_in_Xaml(string input)
{
var xaml = $"<Polygon xmlns='https://github.com/avaloniaui' Points='{input}' />";
var loader = new AvaloniaXamlLoader();
var polygon = (Polygon)loader.Load(xaml);
var points = polygon.Points;
Assert.Equal(2, points.Count);
Assert.Equal(new Point(1, 2), points[0]);
Assert.Equal(new Point(3, 4), points[1]);
}
}
}
Loading…
Cancel
Save