Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorAaron Bockover <abock@microsoft.com>2019-03-06 23:19:20 +0300
committerGitHub <noreply@github.com>2019-03-06 23:19:20 +0300
commitdebfa3d9a52108e87dcf74e4ee6cf73ac010a97d (patch)
treeadddd3c8caed2455770bdac88258d295c3c973b6 /main
parentc60106f9c9b5d39b54659cd84e67be399f2ac9ae (diff)
parentc864ceb44db3ca2a0c1712ab56e2726cca973bf3 (diff)
Merge pull request #282 from xamarin/pr-abock-slightly-better-gtk
GtkNSViewHost: replace the native GtkNSView with a managed one
Diffstat (limited to 'main')
-rw-r--r--main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/CocoaTextViewContent.cs57
-rw-r--r--main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/GtkNSViewHost.cs348
2 files changed, 393 insertions, 12 deletions
diff --git a/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/CocoaTextViewContent.cs b/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/CocoaTextViewContent.cs
index a3d3b632c5..d1c4af7971 100644
--- a/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/CocoaTextViewContent.cs
+++ b/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/CocoaTextViewContent.cs
@@ -31,11 +31,10 @@ using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using MonoDevelop.Components;
-using MonoDevelop.Components.Commands;
using MonoDevelop.Core;
+using MonoDevelop.Core.FeatureConfiguration;
using MonoDevelop.Ide.Commands;
using MonoDevelop.Projects;
-using Microsoft.VisualStudio.Text.Editor.Implementation;
namespace MonoDevelop.TextEditor
{
@@ -73,20 +72,47 @@ namespace MonoDevelop.TextEditor
{
ICocoaTextViewHost textViewHost;
NSView textViewHostControl;
- EmbeddedNSViewControl embeddedControl;
+ GtkNSViewHostControl embeddedControl;
- sealed class EmbeddedNSViewControl : Control
+ static readonly Lazy<bool> useManagedGtkNSViewHost = new Lazy<bool> (
+ () => FeatureSwitchService.IsFeatureEnabled ("ManagedGtkNSViewHost").GetValueOrDefault ());
+
+ abstract class GtkNSViewHostControl : Control
+ {
+ public Gtk.Widget GtkView { get; protected set; }
+ }
+
+ sealed class ManagedGtkNSViewHostControl : GtkNSViewHostControl
+ {
+ public ManagedGtkNSViewHostControl (ICocoaTextViewHost textViewHost)
+ {
+ if (textViewHost == null)
+ throw new ArgumentNullException (nameof (textViewHost));
+
+ GtkView = new Gtk.GtkNSViewHost (textViewHost.HostControl);
+ GtkView.Show ();
+ }
+
+ protected override object CreateNativeWidget<T> ()
+ => GtkView;
+
+ public override bool HasFocus
+ => GtkView.HasFocus;
+
+ public override void GrabFocus ()
+ => GtkView.GrabFocus ();
+ }
+
+ sealed class LegacyGtkNSViewHostControl : GtkNSViewHostControl
{
readonly ICocoaTextViewHost textViewHost;
readonly NSView nsView;
bool nativeViewNeedsFocus;
- public Gtk.Widget GtkView { get; }
-
public bool IsGrabbingFocus { get; private set; }
- public EmbeddedNSViewControl (ICocoaTextViewHost textViewHost)
+ public LegacyGtkNSViewHostControl (ICocoaTextViewHost textViewHost)
{
this.textViewHost = textViewHost ?? throw new ArgumentNullException (nameof (textViewHost));
this.nsView = textViewHost.HostControl;
@@ -164,11 +190,18 @@ namespace MonoDevelop.TextEditor
textViewHost = Imports.TextEditorFactoryService.CreateTextViewHost (TextView, setFocus: true);
textViewHostControl = textViewHost.HostControl;
- embeddedControl = new EmbeddedNSViewControl (textViewHost);
- TextView.GotAggregateFocus += (sender, e) => {
- if (!embeddedControl.IsGrabbingFocus)
- embeddedControl.GtkView.GrabFocus ();
- };
+ if (useManagedGtkNSViewHost.Value) {
+ embeddedControl = new ManagedGtkNSViewHostControl (textViewHost);
+ } else {
+ var legacyEmbeddedControl = new LegacyGtkNSViewHostControl (textViewHost);
+ embeddedControl = legacyEmbeddedControl;
+
+ TextView.GotAggregateFocus += (sender, e) => {
+ if (!legacyEmbeddedControl.IsGrabbingFocus)
+ embeddedControl.GtkView.GrabFocus ();
+ };
+ }
+
TextView.Properties.AddProperty (typeof (Gtk.Widget), embeddedControl.GtkView);
return embeddedControl;
diff --git a/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/GtkNSViewHost.cs b/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/GtkNSViewHost.cs
new file mode 100644
index 0000000000..edc46e22e7
--- /dev/null
+++ b/main/src/addins/MonoDevelop.TextEditor/MonoDevelop.TextEditor.Cocoa/GtkNSViewHost.cs
@@ -0,0 +1,348 @@
+//
+// Copyright (c) Microsoft Corp. (https://www.microsoft.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using AppKit;
+using CoreGraphics;
+using ObjCRuntime;
+
+namespace Gtk
+{
+ sealed class GtkNSViewHost : Widget
+ {
+ const string LIBGTKQUARTZ = "libgtk-quartz-2.0.dylib";
+
+ [DllImport (LIBGTKQUARTZ)]
+ static extern IntPtr gdk_quartz_window_get_nsview (IntPtr window);
+
+ [DllImport (LIBGTKQUARTZ)]
+ static extern IntPtr gdk_quartz_event_get_nsevent (IntPtr evnt);
+
+ [DllImport (LIBGTKQUARTZ)]
+ static extern void gdk_window_coords_to_parent (
+ IntPtr window,
+ double x,
+ double y,
+ out double parent_x,
+ out double parent_y);
+
+ [DllImport (LIBGTKQUARTZ)]
+ static extern bool gdk_window_has_native (IntPtr window);
+
+ static NSEvent GetNSEvent (Gdk.Event evnt)
+ {
+ if (evnt == null || evnt.Handle == IntPtr.Zero)
+ return null;
+
+ var nsEventHandle = gdk_quartz_event_get_nsevent (evnt.Handle);
+ if (nsEventHandle == IntPtr.Zero)
+ return null;
+
+ return Runtime.GetNSObject<NSEvent> (nsEventHandle);
+ }
+
+ NSView view;
+ NSView superview;
+
+ public GtkNSViewHost (NSView view)
+ {
+ this.view = view ?? throw new ArgumentNullException (nameof (view));
+
+ WidgetFlags |= WidgetFlags.NoWindow;
+ }
+
+ void UpdateViewFrame ()
+ {
+ LogEnter ();
+ try {
+ if (view == null)
+ return;
+
+ var window = GdkWindow;
+ var allocation = Allocation;
+ double x = allocation.X;
+ double y = allocation.Y;
+
+ while (window != null && !gdk_window_has_native (window.Handle)) {
+ gdk_window_coords_to_parent (window.Handle, x, y, out var nx, out var ny);
+ Log ($"({x},{y}) -> ({nx},{ny})");
+ x = nx;
+ y = ny;
+ window = window.Parent;
+ }
+
+ view.Frame = new CGRect (x, y, allocation.Width, allocation.Height);
+ Log ($"Frame: {view.Frame}");
+ } finally {
+ LogExit ();
+ }
+ }
+
+ static NSView RecursivelyFindSubviewForPredicate (NSView view, Predicate<NSView> predicate)
+ {
+ if (predicate (view))
+ return view;
+
+ var subviews = view.Subviews;
+ if (subviews != null && subviews.Length > 0) {
+ foreach (var subview in subviews) {
+ if (subview != null) {
+ var foundView = RecursivelyFindSubviewForPredicate (subview, predicate);
+ if (foundView != null)
+ return foundView;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ NSView GetAcceptsFirstResponderView ()
+ => RecursivelyFindSubviewForPredicate (view, v => v.AcceptsFirstResponder ());
+
+ protected override void OnDestroyed ()
+ {
+ LogEnter ();
+
+ view?.RemoveFromSuperview ();
+ view = null;
+ superview = null;
+
+ base.OnDestroyed ();
+
+ LogExit ();
+ }
+
+ protected override void OnRealized ()
+ {
+ LogEnter ();
+
+ GdkWindow = Parent.GdkWindow;
+
+ if (GdkWindow != null && GdkWindow.Handle != IntPtr.Zero) {
+ var superviewHandle = gdk_quartz_window_get_nsview (GdkWindow.Handle);
+ if (superviewHandle != IntPtr.Zero)
+ superview = Runtime.GetNSObject<NSView> (superviewHandle);
+ }
+
+ if (superview != null && view != null) {
+ view.Hidden = true;
+ superview.AddSubview (view);
+ UpdateViewFrame ();
+
+ CanFocus = GetAcceptsFirstResponderView () != null;
+ }
+
+ base.OnRealized ();
+
+ LogExit ();
+ }
+
+ protected override void OnUnrealized ()
+ {
+ LogEnter ();
+
+ if (IsMapped)
+ Unmap ();
+
+ view?.RemoveFromSuperview ();
+ superview = null;
+
+ base.OnUnrealized ();
+ LogExit ();
+ }
+
+ protected override void OnMapped ()
+ {
+ LogEnter ();
+
+ view.Hidden = false;
+
+ base.OnMapped ();
+
+ if (IsMapped)
+ UpdateViewFrame ();
+
+ LogExit ();
+ }
+
+ protected override void OnUnmapped ()
+ {
+ LogEnter ();
+
+ view.Hidden = true;
+
+ base.OnUnmapped ();
+
+ LogExit ();
+ }
+
+ protected override bool OnConfigureEvent (Gdk.EventConfigure evnt)
+ {
+ LogEnter ();
+ try {
+ return base.OnConfigureEvent (evnt);
+ } finally {
+ LogExit ();
+ }
+ }
+
+ protected override void OnSizeRequested (ref Requisition requisition)
+ {
+ LogEnter ();
+ try {
+ if (view == null) {
+ Log ("Calling base. 'view' is null");
+ base.OnSizeRequested (ref requisition);
+ return;
+ }
+
+ var fittingSize = view.FittingSize;
+ requisition.Width = (int)fittingSize.Width;
+ requisition.Height = (int)fittingSize.Height;
+ Log ($"Setting requisition to {requisition.Width}x{requisition.Height}");
+ } finally {
+ LogExit ();
+ }
+ }
+
+ protected override void OnSizeAllocated (Gdk.Rectangle allocation)
+ {
+ LogEnter ();
+
+ base.OnSizeAllocated (allocation);
+
+ UpdateViewFrame ();
+
+ LogExit ();
+ }
+
+ protected override bool OnFocusInEvent (Gdk.EventFocus evnt)
+ {
+ LogEnter ();
+ try {
+ var acceptsFirstResponderView = GetAcceptsFirstResponderView ();
+ if (acceptsFirstResponderView?.Window == null)
+ return false;
+
+ acceptsFirstResponderView.Window.MakeFirstResponder (acceptsFirstResponderView);
+
+ return base.OnFocusInEvent (evnt);
+ } finally {
+ LogExit ();
+ }
+ }
+
+ protected override bool OnFocusOutEvent (Gdk.EventFocus evnt)
+ {
+ LogEnter ();
+ try {
+ var firstResponder = view?.Window.FirstResponder as NSView;
+ if (firstResponder != null && view?.AncestorSharedWithView (firstResponder) == view)
+ firstResponder.Window.MakeFirstResponder (null);
+ return base.OnFocusOutEvent (evnt);
+ } finally {
+ LogExit ();
+ }
+ }
+
+ bool ForwardEvent<TEvent> (
+ TEvent evnt,
+ Action<NSView, NSEvent> forwardCall,
+ Func<TEvent, bool> baseCall) where TEvent : Gdk.Event
+ {
+ var acceptsFirstResponderView = GetAcceptsFirstResponderView ();
+ if (acceptsFirstResponderView == null)
+ return false;
+
+ var nsEvent = GetNSEvent (evnt);
+ if (nsEvent == null)
+ return false;
+
+ forwardCall (acceptsFirstResponderView, nsEvent);
+
+ return baseCall (evnt);
+ }
+
+ protected override bool OnKeyPressEvent (Gdk.EventKey evnt)
+ {
+ LogEnter ();
+ try {
+ return ForwardEvent (
+ evnt,
+ (v, e) => v.KeyDown (e),
+ base.OnKeyReleaseEvent);
+ } finally {
+ LogExit ();
+ }
+ }
+
+ protected override bool OnKeyReleaseEvent (Gdk.EventKey evnt)
+ {
+ LogEnter ();
+ try {
+ return ForwardEvent (
+ evnt,
+ (v, e) => v.KeyUp (e),
+ base.OnKeyReleaseEvent);
+ } finally {
+ LogExit ();
+ }
+ }
+
+ #region Tracing
+
+ int traceDepth = 0;
+
+ [Conditional ("DEBUG")]
+ void LogIndent ()
+ => Debug.Write (new string (' ', traceDepth * 2));
+
+ [Conditional ("DEBUG")]
+ void Log (string message, [CallerMemberName] string memberName = null)
+ {
+ LogIndent ();
+ Debug.WriteLine ($"{memberName}: {message}");
+ }
+
+ [Conditional ("DEBUG")]
+ void LogEnter ([CallerMemberName] string memberName = null)
+ {
+ LogIndent ();
+ Debug.WriteLine ($"Enter: {memberName}");
+ traceDepth++;
+ }
+
+ [Conditional ("DEBUG")]
+ void LogExit ([CallerMemberName] string memberName = null)
+ {
+ traceDepth--;
+ LogIndent ();
+ Debug.WriteLine ($"Exit: {memberName}");
+ }
+
+ #endregion
+ }
+} \ No newline at end of file