Browse Source

Merge branch 'master' into textbox-margin

Jumar Macato 4 years ago
parent
commit
dafb8b2762

+ 6 - 0
native/Avalonia.Native/src/OSX/app.mm

@@ -50,6 +50,12 @@ ComPtr<IAvnApplicationEvents> _events;
     
     _events->FilesOpened(array);
 }
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
+{
+    return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel;
+}
+
 @end
 
 @interface AvnApplication : NSApplication

+ 1 - 12
native/Avalonia.Native/src/OSX/window.mm

@@ -2031,18 +2031,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 
 +(void)closeAll
 {
-    NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]];
-    auto numWindows = [windows count];
-    
-    for(int i = 0; i < numWindows; i++)
-    {
-        auto window = (AvnWindow*)[windows objectAtIndex:i];
-        
-        if([window parentWindow] == nullptr) // Avalonia will handle the child windows.
-        {
-            [window performClose:nil];
-        }
-    }
+    [[NSApplication sharedApplication] terminate:self];
 }
 
 - (void)performClose:(id)sender

+ 4 - 1
src/Avalonia.Controls/ApiCompatBaseline.txt

@@ -30,6 +30,9 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV
 MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
 MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
 MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<System.ComponentModel.CancelEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
 MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
 MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
 EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
@@ -38,4 +41,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalo
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
 MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
-Total Issues: 39
+Total Issues: 42

+ 28 - 1
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using System.Threading;
 using Avalonia.Controls;
@@ -42,9 +43,13 @@ namespace Avalonia.Controls.ApplicationLifetimes
                     "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
             _activeLifetime = this;
         }
-        
+
         /// <inheritdoc/>
         public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
+
+        /// <inheritdoc/>
+        public event EventHandler<CancelEventArgs> ShutdownRequested;
+
         /// <inheritdoc/>
         public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
 
@@ -111,6 +116,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
                 ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args);
             }
 
+            var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>(); 
+
+            if (lifetimeEvents != null)
+                lifetimeEvents.ShutdownRequested += OnShutdownRequested;
+
             _cts = new CancellationTokenSource();
             MainWindow?.Show();
             Dispatcher.UIThread.MainLoop(_cts.Token);
@@ -123,6 +133,23 @@ namespace Avalonia.Controls.ApplicationLifetimes
             if (_activeLifetime == this)
                 _activeLifetime = null;
         }
+        
+        private void OnShutdownRequested(object sender, CancelEventArgs e)
+        {
+            ShutdownRequested?.Invoke(this, e);
+
+            if (e.Cancel)
+                return;
+
+            // When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
+            // shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
+            // owners.
+            foreach (var w in Windows)
+                if (w.Owner is null)
+                    w.Close();
+            if (Windows.Count > 0)
+                e.Cancel = true;
+        }
     }
     
     public class ClassicDesktopStyleApplicationLifetimeOptions

+ 12 - 0
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 
 namespace Avalonia.Controls.ApplicationLifetimes
 {
@@ -34,5 +35,16 @@ namespace Avalonia.Controls.ApplicationLifetimes
         Window MainWindow { get; set; }
         
         IReadOnlyList<Window> Windows { get; }
+
+        /// <summary>
+        /// Raised by the platform when a shutdown is requested.
+        /// </summary>
+        /// <remarks>
+        /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event
+        /// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application
+        /// will try to close each non-owned open window, invoking the <see cref="Window.Closing"/> event on each and allowing
+        /// each window to cancel the shutdown.
+        /// </remarks>
+        event EventHandler<CancelEventArgs> ShutdownRequested;
     }
 }

+ 16 - 0
src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs

@@ -0,0 +1,16 @@
+using System;
+using System.ComponentModel;
+
+namespace Avalonia.Platform
+{
+    public interface IPlatformLifetimeEventsImpl
+    {
+        /// <summary>
+        /// Raised by the platform when a shutdown is requested.
+        /// </summary>
+        /// <remarks>
+        /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
+        /// </remarks>
+        event EventHandler<CancelEventArgs> ShutdownRequested;
+    }
+}

+ 12 - 1
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@@ -1,14 +1,25 @@
 using System;
+using System.ComponentModel;
 using Avalonia.Native.Interop;
 using Avalonia.Platform;
 
 namespace Avalonia.Native
 {
-    internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents
+    internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
     {
+        public event EventHandler<CancelEventArgs> ShutdownRequested;
+        
         void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
         {
             ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
         }
+
+        public int TryShutdown()
+        {
+            if (ShutdownRequested is null) return 1;
+            var e = new CancelEventArgs();
+            ShutdownRequested(this, e);
+            return (!e.Cancel).AsComBool();
+        }
     }
 }

+ 2 - 1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@@ -111,7 +111,8 @@ namespace Avalonia.Native
                 .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
                 .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
                 .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
-                .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory));
+                .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
+                .Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform);
 
             if (_options.UseGpu)
             {

+ 1 - 0
src/Avalonia.Native/avn.idl

@@ -733,4 +733,5 @@ interface IAvnNativeControlHostTopLevelAttachment : IUnknown
 interface IAvnApplicationEvents : IUnknown
 {
      void FilesOpened (IAvnStringArray* urls);
+     bool TryShutdown();
 }

+ 29 - 1
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Platform;
 using Avalonia.Threading;
@@ -209,6 +210,33 @@ namespace Avalonia.Controls.UnitTests
                 Assert.Empty(lifetime.Windows);
             }
         }
+
+        [Fact]
+        public void Should_Allow_Canceling_Shutdown_Via_ShutdownRequested_Event()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            using (var lifetime = new ClassicDesktopStyleApplicationLifetime())
+            {
+                var lifetimeEvents = new Mock<IPlatformLifetimeEventsImpl>();
+                AvaloniaLocator.CurrentMutable.Bind<IPlatformLifetimeEventsImpl>().ToConstant(lifetimeEvents.Object);
+                lifetime.Start(Array.Empty<string>());
+
+                var window = new Window();
+                var raised = 0;
+
+                window.Show();
+
+                lifetime.ShutdownRequested += (s, e) =>
+                {
+                    e.Cancel = true;
+                    ++raised;
+                };
+
+                lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs());
+
+                Assert.Equal(1, raised);
+                Assert.Equal(new[] { window }, lifetime.Windows);
+            }
+        }
     }
-    
 }