From 4a7d4f06a377954a5af92f32720e2b5edfc1a6f1 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Thu, 8 Aug 2019 11:51:50 -0400 Subject: [Debugger] Implemented a Cocoa-based ObjectValueTreeView --- ...noDevelop.Debugger.ExpressionEvaluatorDialog.cs | 1 - .../PreviewWindowManager.cs | 8 + .../MonoDevelop.Debugger.Tests.csproj | 1 + .../ObjectValueTreeViewControllerTests.cs | 353 +++++++ .../PinnedWatches/PinnedWatchAdornmentManager.cs | 190 ++++ .../PinnedWatches/PinnedWatchProvider.cs | 54 + .../PinnedWatches/PinnedWatchView.cs | 195 ++++ .../QuickInfo/DebuggerQuickInfoSource.cs | 68 +- .../QuickInfo/IDebugInfoProvider.cs | 2 +- .../QuickInfo/MacDebuggerTooltipWindow.cs | 158 +++ .../MonoDevelop.Debugger.csproj | 18 + .../MonoDevelop.Debugger/DebugValueWindow.cs | 39 +- .../DebuggerOptionsPanelWidget.cs | 2 +- .../MonoDevelop.Debugger/ExceptionCaughtDialog.cs | 4 +- .../MonoDevelop.Debugger/LocalsPad.cs | 41 +- .../ObjectValue/AddNewExpressionObjectValueNode.cs | 35 + .../ObjectValue/DebuggerObjectValueNode.cs | 6 +- .../ObjectValue/Gtk/GtkObjectValueTreeView.cs | 196 ++-- .../ObjectValue/IObjectValueTreeView.cs | 24 +- .../ObjectValue/LoadingObjectValueNode.cs | 38 + .../Mac/MacDebuggerObjectCellViewBase.cs | 204 ++++ .../ObjectValue/Mac/MacDebuggerObjectNameView.cs | 288 ++++++ .../ObjectValue/Mac/MacDebuggerObjectPinView.cs | 155 +++ .../ObjectValue/Mac/MacDebuggerObjectTypeView.cs | 70 ++ .../ObjectValue/Mac/MacDebuggerObjectValueView.cs | 381 ++++++++ .../ObjectValue/Mac/MacObjectValueNode.cs | 50 + .../ObjectValue/Mac/MacObjectValueTreeView.cs | 1030 ++++++++++++++++++++ .../Mac/MacObjectValueTreeViewDataSource.cs | 343 +++++++ .../Mac/MacObjectValueTreeViewDelegate.cs | 155 +++ .../ObjectValue/ObjectValueTreeView.cs | 12 +- .../ObjectValue/ObjectValueTreeViewController.cs | 213 ++-- .../ObjectValue/ObjectValueTreeViewFakes.cs | 14 +- .../ObjectValue/RootObjectValueNode.cs | 5 + .../MonoDevelop.Debugger/ObjectValuePad.cs | 96 +- .../MonoDevelop.Debugger/PinnedWatch.cs | 47 +- .../MonoDevelop.Debugger/PinnedWatchLocation.cs | 65 ++ .../MonoDevelop.Debugger/WatchPad.cs | 15 +- .../DebugValueTooltipProvider.cs | 18 +- .../MonoDevelop.SourceEditor/PinnedWatchWidget.cs | 2 +- .../MonoDevelop.Components/PopoverWindow.cs | 14 + .../MonoDevelop.Ide.Gui/PadFontChanger.cs | 2 +- 41 files changed, 4274 insertions(+), 338 deletions(-) create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/ObjectValueTreeViewControllerTests.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchAdornmentManager.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchProvider.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchView.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/MacDebuggerTooltipWindow.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/AddNewExpressionObjectValueNode.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/LoadingObjectValueNode.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectCellViewBase.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectNameView.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectPinView.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectTypeView.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectValueView.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueNode.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeView.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDataSource.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDelegate.cs create mode 100644 main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatchLocation.cs (limited to 'main/src') diff --git a/main/src/addins/MonoDevelop.Debugger/Gui/MonoDevelop.Debugger.ExpressionEvaluatorDialog.cs b/main/src/addins/MonoDevelop.Debugger/Gui/MonoDevelop.Debugger.ExpressionEvaluatorDialog.cs index 38404d7c74..1943ffd98c 100644 --- a/main/src/addins/MonoDevelop.Debugger/Gui/MonoDevelop.Debugger.ExpressionEvaluatorDialog.cs +++ b/main/src/addins/MonoDevelop.Debugger/Gui/MonoDevelop.Debugger.ExpressionEvaluatorDialog.cs @@ -72,7 +72,6 @@ namespace MonoDevelop.Debugger this.valueTree.AllowPinning = false; this.valueTree.RootPinAlwaysVisible = false; this.valueTree.AllowExpanding = false; - this.valueTree.PinnedWatchLine = 0; this.valueTree.CompactView = false; this.GtkScrolledWindow.Add (this.valueTree); this.vbox2.Add (this.GtkScrolledWindow); diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.PreviewVisualizers/PreviewWindowManager.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.PreviewVisualizers/PreviewWindowManager.cs index 3bdfa4f776..f694826222 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.PreviewVisualizers/PreviewWindowManager.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.PreviewVisualizers/PreviewWindowManager.cs @@ -47,10 +47,16 @@ namespace MonoDevelop.Debugger wnd = new PreviewVisualizerWindow (val, widget); IdeApp.CommandService.RegisterTopWindow (wnd); wnd.ShowPopup (widget, previewButtonArea, PopupPosition.Left); + wnd.FocusOutEvent += HandleFocusOutEvent; wnd.Destroyed += HandleDestroyed; OnWindowShown (EventArgs.Empty); } + private static void HandleFocusOutEvent (object o, Gtk.FocusOutEventArgs args) + { + DestroyWindow (); + } + static void HandleDestroyed (object sender, EventArgs e) { wnd = null; @@ -85,6 +91,8 @@ namespace MonoDevelop.Debugger public static void DestroyWindow () { if (wnd != null) { + wnd.FocusOutEvent -= HandleFocusOutEvent; + wnd.Destroyed -= HandleDestroyed; wnd.Destroy (); wnd = null; } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/MonoDevelop.Debugger.Tests.csproj b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/MonoDevelop.Debugger.Tests.csproj index 6007317a64..44807cad5e 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/MonoDevelop.Debugger.Tests.csproj +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/MonoDevelop.Debugger.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/ObjectValueTreeViewControllerTests.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/ObjectValueTreeViewControllerTests.cs new file mode 100644 index 0000000000..6c1e702e39 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Tests/ObjectValueTreeViewControllerTests.cs @@ -0,0 +1,353 @@ +// +// ObjectValueTreeViewControllerTests.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +using NUnit.Framework; + +using Mono.Debugging.Client; + +namespace MonoDevelop.Debugger.Tests +{ + class DummyDebuggerService : IDebuggerService + { + public bool IsConnected => true; + + public bool IsPaused => true; + + public bool HasInlineVisualizer (ObjectValueNode node) + { + return false; + } + + public bool HasValueVisualizers (ObjectValueNode node) + { + return false; + } + + public void NotifyVariableChanged () + { + } + + public bool ShowValueVisualizer (ObjectValueNode node) + { + return false; + } + } + + class DummyStackFrame : IStackFrame + { + public EvaluationOptions CloneSessionEvaluationOpions () + { + return new EvaluationOptions (); + } + + public ObjectValueNode EvaluateExpression (string expression) + { + return new FakeObjectValueNode (expression); + } + + public ObjectValueNode [] EvaluateExpressions (IList expressions) + { + var values = new ObjectValueNode [expressions.Count]; + + for (int i = 0; i < expressions.Count; i++) + values [i] = new FakeObjectValueNode (expressions [i]); + + return values; + } + } + + class DummyObjectValueTreeViewController : ObjectValueTreeViewController + { + protected override IDebuggerService OnGetDebuggerService () + { + return new DummyDebuggerService (); + } + + public void SetViewControl (IObjectValueTreeView control) + { + ConfigureView (control); + } + } + + class ObjectValueNodeReplacedEventArgs : EventArgs + { + public ObjectValueNodeReplacedEventArgs (ObjectValueNode node, ObjectValueNode[] replacementNodes) + { + Node = node; + ReplacementNodes = replacementNodes; + } + + public ObjectValueNode Node { + get; private set; + } + + public ObjectValueNode[] ReplacementNodes { + get; private set; + } + } + + class DummyObjectValueTreeView : IObjectValueTreeView + { + public bool AllowEditing { get; set; } + public bool AllowExpanding { get; set; } + public PinnedWatch PinnedWatch { get; set; } + + public int PinnedWatchOffset { get; set; } + + public event EventHandler NodeExpand; + + public object EmitNodeExpand (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeExpand (this, args); + return args.Response; + } + + public event EventHandler NodeCollapse; + + public object EmitNodeCollapse (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeCollapse (this, args); + return args.Response; + } + + public event EventHandler NodeLoadMoreChildren; + + public object EmitNodeLoadMoreChildren (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeLoadMoreChildren (this, args); + return args.Response; + } + + public event EventHandler NodeRefresh; + + public object EmitNodeRefresh (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeRefresh (this, args); + return args.Response; + } + + public event EventHandler NodeGetCanEdit; + + public object EmitNodeGetCanEdit (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeGetCanEdit (this, args); + return args.Response; + } + + public event EventHandler NodeEditValue; + + public object EmitNodeEditValue (ObjectValueNode node, string newValue) + { + var args = new ObjectValueEditEventArgs (node, newValue); + NodeEditValue (this, args); + return args.Response; + } + + public event EventHandler NodeRemoved; + + public object EmitNodeRemoved (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeRemoved (this, args); + return args.Response; + } + + public event EventHandler NodePinned; + + public object EmitNodePinned (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodePinned (this, args); + return args.Response; + } + + public event EventHandler NodeUnpinned; + + public object EmitNodeUnpinned (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeUnpinned (this, args); + return args.Response; + } + + public event EventHandler NodeShowVisualiser; + + public object EmitNodeShowVisualizer (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeShowVisualiser (this, args); + return args.Response; + } + + public event EventHandler ExpressionAdded; + + public object EmitExpressionAdded (ObjectValueNode node, string expression) + { + var args = new ObjectValueExpressionEventArgs (node, expression); + ExpressionAdded (this, args); + return args.Response; + } + + public event EventHandler ExpressionEdited; + + public object EmitExpressionEdited (ObjectValueNode node, string expression) + { + var args = new ObjectValueExpressionEventArgs (node, expression); + ExpressionEdited (this, args); + return args.Response; + } + + public event EventHandler StartEditing; + public event EventHandler EndEditing; + + public event EventHandler ViewAppendedNode; + + public void Appended (ObjectValueNode node) + { + ViewAppendedNode.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + public void Appended (IList nodes) + { + foreach (var node in nodes) + ViewAppendedNode?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + public event EventHandler ViewCleared; + + public void Cleared () + { + ViewCleared?.Invoke (this, EventArgs.Empty); + } + + public event EventHandler ViewReplacedNode; + + public void LoadEvaluatedNode (ObjectValueNode node, ObjectValueNode[] replacementNodes) + { + ViewReplacedNode?.Invoke (this, new ObjectValueNodeReplacedEventArgs (node, replacementNodes)); + } + + public event EventHandler ViewLoadedChildren; + + public void LoadNodeChildren (ObjectValueNode node, int startIndex, int count) + { + ViewLoadedChildren?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + public event EventHandler ViewExpandedNode; + + public void OnNodeExpanded (ObjectValueNode node) + { + ViewExpandedNode?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + } + + [TestFixture] + public class ObjectValueTreeViewControllerTests + { + [Test] + public async Task TestBasicFunctionalityAsync () + { + var controller = new DummyObjectValueTreeViewController (); + var view = new DummyObjectValueTreeView (); + ObjectValueNode[] replacements = null; + int appended = 0; + int replaced = 0; + int expanded = 0; + int cleared = 0; + int loaded = 0; + + view.ViewAppendedNode += (o, e) => { + appended++; + }; + + view.ViewReplacedNode += (o, e) => { + replaced++; + replacements = e.ReplacementNodes; + }; + + view.ViewLoadedChildren += (o, e) => { + loaded++; + }; + + view.ViewExpandedNode += (o, e) => { + expanded++; + }; + + view.ViewCleared += (o, e) => { + cleared++; + }; + + controller.SetViewControl (view); + controller.Frame = new DummyStackFrame (); + + var xx = new List (); + + xx.Add (new FakeObjectValueNode ("f1")); + xx.Add (new FakeIsImplicitNotSupportedObjectValueNode ()); + + xx.Add (new FakeEvaluatingGroupObjectValueNode (1)); + xx.Add (new FakeEvaluatingGroupObjectValueNode (0)); + xx.Add (new FakeEvaluatingGroupObjectValueNode (5)); + + xx.Add (new FakeEvaluatingObjectValueNode ()); + xx.Add (new FakeEnumerableObjectValueNode (10)); + xx.Add (new FakeEnumerableObjectValueNode (20)); + xx.Add (new FakeEnumerableObjectValueNode (23)); + + controller.AddValues (xx); + + Assert.AreEqual (xx.Count, appended, "Number of appended object value nodes do not match."); + + // the fake evaluating nodes are using a 5000 timer, so 5100 should be enough... + await Task.Delay (5100); + + Assert.AreEqual (4, replaced, "Number of replaced nodes does not match."); + + // expand the "f1" node + view.EmitNodeExpand (xx[0]); + + // expanding a fake node uses a 1000 timer, so 1100 should be enough + await Task.Delay (1100); + + Assert.AreEqual (1, expanded, "Expected the f1 node to be expanded."); + + controller.ClearAll (); + + Assert.AreEqual (1, cleared, "Expected ClearAll to clear the values."); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchAdornmentManager.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchAdornmentManager.cs new file mode 100644 index 0000000000..51539ac2cc --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchAdornmentManager.cs @@ -0,0 +1,190 @@ +// +// PinnedWatchAdornmentManager.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Collections.Generic; + +using AppKit; +using CoreGraphics; + +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Adornments; + +using MonoDevelop.Core; + +namespace MonoDevelop.Debugger.VSTextView.PinnedWatches +{ + sealed class PinnedWatchAdornmentManager : IDisposable + { + readonly Dictionary adornments = new Dictionary (); + readonly ICocoaViewFactory cocoaViewFactory; + readonly IXPlatAdornmentLayer layer; + readonly ICocoaTextView textView; + readonly string path; + bool debugging; + + public PinnedWatchAdornmentManager (ICocoaViewFactory cocoaViewFactory, ICocoaTextView textView) + { + path = textView.TextBuffer.GetFilePathOrNull (); + + if (path == null) + return; + + DebuggingService.PinnedWatches.WatchAdded += OnWatchAdded; + DebuggingService.PinnedWatches.WatchChanged += OnWatchChanged; + DebuggingService.PinnedWatches.WatchRemoved += OnWatchRemoved; + DebuggingService.DebugSessionStarted += OnDebugSessionStarted; + DebuggingService.StoppedEvent += OnDebuggingSessionStopped; + + this.layer = textView.GetXPlatAdornmentLayer ("PinnedWatch"); + this.cocoaViewFactory = cocoaViewFactory; + this.textView = textView; + + //this.textView.LayoutChanged += OnTextViewLayoutChanged; + + if (DebuggingService.IsDebugging) { + RenderAllAdornments (); + debugging = true; + } + } + + void OnWatchAdded (object sender, PinnedWatchEventArgs e) + { + if (!debugging || e.Watch.File != path) + return; + + RenderAdornment (e.Watch); + } + + void OnWatchChanged (object sender, PinnedWatchEventArgs e) + { + if (!debugging || e.Watch.File != path) + return; + + if (!adornments.TryGetValue (e.Watch, out var adornment)) + return; + + var view = (PinnedWatchView) ((ICocoaMaterialView) adornment).ContentView; + + view.SetObjectValue (e.Watch.Value); + } + + void OnWatchRemoved (object sender, PinnedWatchEventArgs e) + { + if (!debugging || e.Watch.File != path) + return; + + layer.RemoveAdornmentsByTag (e.Watch); + adornments.Remove (e.Watch); + } + + void RenderAdornment (PinnedWatch watch) + { + var newSpan = textView.TextSnapshot.SpanFromMDColumnAndLine (watch.Line, watch.Column, watch.EndLine, watch.EndColumn); + var trackingSpan = textView.TextSnapshot.CreateTrackingSpan (newSpan, SpanTrackingMode.EdgeInclusive); + var span = trackingSpan.GetSpan (textView.TextSnapshot); + + if (textView.TextViewLines == null) + return; + + if (!textView.TextViewLines.FormattedSpan.Contains (span.End)) + return; + + var pinnedWatchView = new PinnedWatchView (watch, DebuggingService.CurrentFrame); + var materialView = cocoaViewFactory.CreateMaterialView (); + materialView.Material = NSVisualEffectMaterial.WindowBackground; + materialView.ContentView = pinnedWatchView; + materialView.CornerRadius = 3; + + var view = (NSView) materialView; + view.WantsLayer = true; + + try { + var charBound = textView.TextViewLines.GetCharacterBounds (span.End); + var origin = new CGPoint ( + Math.Round (charBound.Left), + Math.Round (charBound.TextTop + charBound.TextHeight / 2 - view.Frame.Height / 2)); + view.SetFrameOrigin (origin); + } catch (Exception ex) { + view.SetFrameOrigin (default); + LoggingService.LogInternalError ("https://vsmac.dev/923058", ex); + } + + layer.AddAdornment (XPlatAdornmentPositioningBehavior.TextRelative, span, watch, view, null); + adornments[watch] = view; + } + + void RenderAllAdornments () + { + foreach (var watch in DebuggingService.PinnedWatches.GetWatchesForFile (path)) + RenderAdornment (watch); + } + + void OnDebugSessionStarted (object sender, EventArgs e) + { + if (debugging || !DebuggingService.IsDebugging) + return; + + RenderAllAdornments (); + debugging = true; + } + + void OnDebuggingSessionStopped (object sender, EventArgs e) + { + if (DebuggingService.IsDebugging) + return; + + layer.RemoveAllAdornments (); + adornments.Clear (); + debugging = false; + } + + //void OnTextViewLayoutChanged (object sender, TextViewLayoutChangedEventArgs e) + //{ + // if (!DebuggingService.IsDebugging) + // return; + + // layer.RemoveAllAdornments (); + // adornments.Clear (); + + // RenderAllAdornments (); + //} + + public void Dispose () + { + if (path == null) + return; + + DebuggingService.PinnedWatches.WatchAdded -= OnWatchAdded; + DebuggingService.PinnedWatches.WatchChanged -= OnWatchChanged; + DebuggingService.PinnedWatches.WatchRemoved -= OnWatchRemoved; + DebuggingService.DebugSessionStarted -= OnDebugSessionStarted; + DebuggingService.StoppedEvent -= OnDebuggingSessionStopped; + //textView.LayoutChanged -= OnTextViewLayoutChanged; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchProvider.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchProvider.cs new file mode 100644 index 0000000000..a73bcee1fe --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchProvider.cs @@ -0,0 +1,54 @@ +// +// PinnedWatchProvider.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace MonoDevelop.Debugger.VSTextView.PinnedWatches +{ + [Export (typeof (ICocoaTextViewCreationListener))] + [ContentType ("text")] + [TextViewRole (PredefinedTextViewRoles.Debuggable)] + sealed class PinnedWatchProvider : ICocoaTextViewCreationListener + { + [Import] + internal ICocoaViewFactory cocoaViewFactory; + + public void TextViewCreated (ICocoaTextView textView) + { + var manager = new PinnedWatchAdornmentManager (cocoaViewFactory, textView); + textView.Closed += (s, e) => manager.Dispose (); + } + + [Export] + [Name ("PinnedWatch")] + [Order (After = PredefinedAdornmentLayers.Caret)] + internal AdornmentLayerDefinition visibleWhitespaceLayer; + } +} \ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchView.cs new file mode 100644 index 0000000000..eee1df7af6 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/PinnedWatches/PinnedWatchView.cs @@ -0,0 +1,195 @@ +// +// MacPinnedWatchView.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 AppKit; + +using Mono.Debugging.Client; + +namespace MonoDevelop.Debugger.VSTextView.PinnedWatches +{ + sealed class PinnedWatchView : NSScrollView + { + readonly ObjectValueTreeViewController controller; + readonly NSLayoutConstraint heightConstraint; + readonly NSLayoutConstraint widthConstraint; + readonly MacObjectValueTreeView treeView; + NSLayoutConstraint superHeightConstraint; + NSLayoutConstraint superWidthConstraint; + ObjectValue objectValue; + bool disposed; + + public PinnedWatchView (PinnedWatch watch, StackFrame frame) + { + HasVerticalScroller = true; + AutohidesScrollers = true; + + controller = new ObjectValueTreeViewController (); + controller.SetStackFrame (frame); + controller.AllowEditing = true; + + treeView = controller.GetMacControl (headersVisible: false, compactView: true, allowPinning: true); + + controller.PinnedWatch = watch; + + if (watch.Value != null) + controller.AddValue (watch.Value); + + var rect = treeView.Frame; + + if (rect.Height < 1) + treeView.Frame = new CoreGraphics.CGRect (rect.X, rect.Y, rect.Width, 19); + + DocumentView = treeView; + Frame = treeView.Frame; + + heightConstraint = HeightAnchor.ConstraintEqualToConstant (treeView.Frame.Height); + heightConstraint.Active = true; + + widthConstraint = WidthAnchor.ConstraintEqualToConstant (treeView.Frame.Width); + widthConstraint.Active = true; + + DebuggingService.ResumedEvent += OnDebuggerResumed; + DebuggingService.PausedEvent += OnDebuggerPaused; + treeView.Resized += OnTreeViewResized; + } + + public void SetObjectValue (ObjectValue value) + { + if (value == objectValue) + return; + + controller.ClearAll (); + + if (value != null) + controller.AddValue (value); + + objectValue = value; + } + + public override void ViewDidMoveToSuperview () + { + base.ViewDidMoveToSuperview (); + + if (Superview != null) { + superHeightConstraint = Superview.HeightAnchor.ConstraintEqualToConstant (Frame.Height); + superWidthConstraint = Superview.WidthAnchor.ConstraintEqualToConstant (Frame.Width); + superHeightConstraint.Active = true; + superWidthConstraint.Active = true; + } else { + superHeightConstraint?.Dispose (); + superWidthConstraint?.Dispose (); + superHeightConstraint = null; + superWidthConstraint = null; + } + } + + void OnTreeViewResized (object sender, EventArgs e) + { + //const string CocoaTextViewScrollView = "CocoaTextViewScrollView"; + const string CocoaTextViewControl = "CocoaTextViewControl"; + //const string CocoaEditorGridView = "CocoaEditorGridView"; + + var materialView = Superview; + + // Find our parent CocoaTextViewControl + var textView = materialView; + while (textView != null && textView.GetType ().Name != CocoaTextViewControl) + textView = textView.Superview; + + if (textView == null) + return; + + var origin = textView.ConvertPointFromView (Frame.Location, this); + var maxHeight = NMath.Max (textView.Frame.Bottom - origin.Y, treeView.RowHeight * 2); + var height = treeView.FittingSize.Height; + var width = treeView.Frame.Width; + + height = NMath.Min (height, maxHeight); + + heightConstraint.Constant = height; + widthConstraint.Constant = width; + + superHeightConstraint.Constant = height; + superWidthConstraint.Constant = width; + +#if REPARENT_SO_SCROLLING_WORKS + // Find our parent CocoaEditorGridView + var gridView = textView.Superview; + while (gridView != null && gridView.GetType ().Name != CocoaEditorGridView) + gridView = gridView.Superview; + + if (gridView == null) + return; + + // Find the CocoaTextViewScrollView + NSView textViewScrollView = null; + foreach (var child in gridView.Subviews) { + if (child.GetType ().Name == CocoaTextViewScrollView) { + textViewScrollView = child; + break; + } + } + + materialView.RemoveFromSuperview (); + + gridView.AddSubview (materialView, NSWindowOrderingMode.Above, textViewScrollView); +#endif + } + + void OnDebuggerResumed (object sender, EventArgs e) + { + controller.ChangeCheckpoint (); + controller.AllowExpanding = false; + controller.AllowEditing = false; + } + + void OnDebuggerPaused (object sender, EventArgs e) + { + controller.AllowExpanding = true; + controller.AllowEditing = true; + } + + protected override void Dispose (bool disposing) + { + if (disposing && !disposed) { + DebuggingService.ResumedEvent -= OnDebuggerResumed; + DebuggingService.PausedEvent -= OnDebuggerPaused; + treeView.Resized -= OnTreeViewResized; + superHeightConstraint?.Dispose (); + superWidthConstraint?.Dispose (); + superHeightConstraint = null; + superWidthConstraint = null; + heightConstraint.Dispose (); + widthConstraint.Dispose (); + disposed = true; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/DebuggerQuickInfoSource.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/DebuggerQuickInfoSource.cs index 023af1e8cf..7ef683ad20 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/DebuggerQuickInfoSource.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/DebuggerQuickInfoSource.cs @@ -1,11 +1,14 @@ using System; using System.Threading; using System.Threading.Tasks; + +using Gtk; + using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; -using MonoDevelop.Core; using Microsoft.VisualStudio.Text.Editor; -using Gtk; + +using MonoDevelop.Core; using MonoDevelop.Ide.Gui.Documents; namespace MonoDevelop.Debugger.VSTextView.QuickInfo @@ -14,9 +17,11 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo { readonly DebuggerQuickInfoSourceProvider provider; readonly ITextBuffer textBuffer; - DebugValueWindow window; - ITextView lastView; DocumentView lastDocumentView; +#if MAC + MacDebuggerTooltipWindow window; +#endif + ITextView lastView; public DebuggerQuickInfoSource (DebuggerQuickInfoSourceProvider provider, ITextBuffer textBuffer) { @@ -24,7 +29,6 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo this.textBuffer = textBuffer; DebuggingService.CurrentFrameChanged += CurrentFrameChanged; DebuggingService.StoppedEvent += TargetProcessExited; - } void CurrentFrameChanged (object sender, EventArgs e) @@ -79,10 +83,11 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo { if (DebuggingService.CurrentFrame == null) return null; + if (window != null) await Runtime.RunInMainThread (DestroyWindow); - var view = session.TextView; + var view = session.TextView; var textViewLines = view.TextViewLines; var snapshot = textViewLines.FormattedSpan.Snapshot; var triggerPoint = session.GetTriggerPoint (textBuffer); @@ -110,6 +115,7 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo return null; } } + return null; } @@ -130,7 +136,7 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo if (val == null || val.IsUnknown || val.IsNotSupported) return; - if (!view.Properties.TryGetProperty (typeof (Gtk.Widget), out Gtk.Widget gtkParent)) + if (!view.Properties.TryGetProperty (typeof (Widget), out Widget gtkParent)) return; provider.textDocumentFactoryService.TryGetTextDocument (view.TextDataModel.DocumentBuffer, out var textDocument); @@ -140,22 +146,38 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo // and do our own thing, notice VS does same thing await session.DismissAsync (); await provider.joinableTaskContext.Factory.SwitchToMainThreadAsync (); - this.lastView = view; + lastView = view; + val.Name = debugInfo.Text; - window = new DebugValueWindow ((Gtk.Window)gtkParent.Toplevel, textDocument?.FilePath, textBuffer.CurrentSnapshot.GetLineNumberFromPosition (debugInfo.Span.GetStartPoint (textBuffer.CurrentSnapshot)), DebuggingService.CurrentFrame, val, null); - Ide.IdeApp.CommandService.RegisterTopWindow (window); - var bounds = view.TextViewLines.GetCharacterBounds (point); + +#if MAC + var location = new PinnedWatchLocation (textDocument?.FilePath); + var snapshot = view.TextDataModel.DocumentBuffer.CurrentSnapshot; + int line, column; + + var start = debugInfo.Span.GetStartPoint (snapshot); + snapshot.GetLineAndColumn (start, out line, out column); + location.Column = column; + location.Line = line; + + var end = debugInfo.Span.GetEndPoint (snapshot); + snapshot.GetLineAndColumn (end, out line, out column); + location.EndColumn = column; + location.EndLine = line; + + window = new MacDebuggerTooltipWindow (location, DebuggingService.CurrentFrame, val, watch: null); + view.LayoutChanged += LayoutChanged; #if CLOSE_ON_FOCUS_LOST view.LostAggregateFocus += View_LostAggregateFocus; #endif RegisterForHiddenAsync (view).Ignore (); - window.LeaveNotifyEvent += LeaveNotifyEvent; -#if MAC - var cocoaView = ((ICocoaTextView)view); - var cgPoint = cocoaView.VisualElement.ConvertPointToView (new CoreGraphics.CGPoint (bounds.Left - view.ViewportLeft, bounds.Top - view.ViewportTop), cocoaView.VisualElement.Superview); - cgPoint.Y = cocoaView.VisualElement.Superview.Frame.Height - cgPoint.Y; - window.ShowPopup (gtkParent, new Gdk.Rectangle ((int)cgPoint.X, (int)cgPoint.Y, (int)bounds.Width, (int)bounds.Height), Components.PopupPosition.TopLeft); + + var cocoaView = (ICocoaTextView) view; + var bounds = view.TextViewLines.GetCharacterBounds (point); + var rect = new CoreGraphics.CGRect (bounds.Left - view.ViewportLeft, bounds.Top - view.ViewportTop, bounds.Width, bounds.Height); + + window.Show (rect, cocoaView.VisualElement, AppKit.NSRectEdge.MaxXEdge); #else throw new NotImplementedException (); #endif @@ -173,6 +195,7 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo { DestroyWindow (); } + #if CLOSE_ON_FOCUS_LOST private void View_LostAggregateFocus (object sender, EventArgs e) { @@ -186,6 +209,7 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo #endif } #endif + private void LayoutChanged (object sender, TextViewLayoutChangedEventArgs e) { if (e.OldViewState.ViewportLeft != e.NewViewState.ViewportLeft || @@ -195,19 +219,11 @@ namespace MonoDevelop.Debugger.VSTextView.QuickInfo DestroyWindow (); } - private void LeaveNotifyEvent (object o, LeaveNotifyEventArgs args) - { - if(args.Event.Detail != Gdk.NotifyType.Nonlinear) - return; - DestroyWindow (); - } - void DestroyWindow () { Runtime.AssertMainThread (); if (window != null) { - window.Destroy (); - window.LeaveNotifyEvent -= LeaveNotifyEvent; + window.Close (); window = null; } if (lastView != null) { diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/IDebugInfoProvider.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/IDebugInfoProvider.cs index 94dc291a48..1effd8017b 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/IDebugInfoProvider.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/IDebugInfoProvider.cs @@ -1,7 +1,7 @@ using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.Text; -using System.Threading; namespace MonoDevelop.Debugger.VSTextView.QuickInfo { diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/MacDebuggerTooltipWindow.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/MacDebuggerTooltipWindow.cs new file mode 100644 index 0000000000..dc60ce593e --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.VSTextView/QuickInfo/MacDebuggerTooltipWindow.cs @@ -0,0 +1,158 @@ +// +// MacDebuggerTooltipWindow.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 AppKit; + +using Mono.Debugging.Client; + +namespace MonoDevelop.Debugger +{ + sealed class MacDebuggerTooltipWindow : NSPopover + { + readonly ObjectValueTreeViewController controller; + readonly NSLayoutConstraint heightConstraint; + readonly NSLayoutConstraint widthConstraint; + readonly MacObjectValueTreeView treeView; + readonly NSScrollView scrollView; + bool disposed; + + public MacDebuggerTooltipWindow (PinnedWatchLocation location, StackFrame frame, ObjectValue value, PinnedWatch watch) + { + Animates = false; + Behavior = NSPopoverBehavior.Semitransient; + + controller = new ObjectValueTreeViewController (); + controller.SetStackFrame (frame); + controller.AllowEditing = true; + controller.PinnedWatch = watch; + controller.PinnedWatchLocation = location; + + treeView = controller.GetMacControl (headersVisible: false, allowPinning: true, compactView: true, rootPinVisible: true); + treeView.NodePinned += OnPinStatusChanged; + treeView.StartEditing += OnStartEditing; + treeView.EndEditing += OnEndEditing; + controller.AddValue (value); + + scrollView = new NSScrollView { + HasVerticalScroller = true, + AutohidesScrollers = true, + DocumentView = treeView, + Frame = treeView.Frame + }; + + ContentViewController = new NSViewController { + View = scrollView + }; + + widthConstraint = scrollView.WidthAnchor.ConstraintEqualToAnchor (treeView.WidthAnchor); + widthConstraint.Active = true; + + heightConstraint = scrollView.HeightAnchor.ConstraintEqualToConstant (treeView.Frame.Height); + heightConstraint.Active = true; + + treeView.Resized += OnTreeViewResized; + } + + public DebuggerSession GetDebuggerSession () + { + return controller.GetStackFrame ()?.DebuggerSession; + } + + static nfloat GetMaxHeight (NSWindow window) + { + var visibleFrame = window.Screen.VisibleFrame; + + // Note: You would think that we could make use of the full VisualFrame height, + // but macOS will not actually make our tooltip window that large no matter + // what. + // + // The downside of *trying* to use the full VisualFrame height is that the + // scrollView will think that it is that tall when it in fact is not, thereby + // making it impossible to scroll all the way to the top (and/or, potentially, + // the bottom). + // + // On my machine, the VisualFrame height is 972 (Frame height is 1050 with a + // menubar 23 pixels tall and a dock that is 55 pixels tall). + // + // macOS does not seem to allow the tooltip window to get larger than 943 pixels + // which is 29 pixels shorter than the VisualFrame height. Let's just round that + // up to 30 pixels. + + return visibleFrame.Height - 30; + } + + void OnTreeViewResized (object sender, EventArgs e) + { + var maxHeight = GetMaxHeight (treeView.Window); + var height = treeView.FittingSize.Height; + + height = NMath.Min (height, maxHeight); + + heightConstraint.Constant = height; + } + + void OnPinStatusChanged (object sender, EventArgs args) + { + Close (); + } + + void OnStartEditing (object sender, EventArgs args) + { + //Modal = true; + //PresentViewControllerAsModalWindow (this); + } + + void OnEndEditing (object sender, EventArgs args) + { + //Modal = false; + } + + void PreviewWindowManager_WindowClosed (object sender, EventArgs e) + { + // When Preview window is closed we want to put focus(IsActive=true) back on DebugValueWindow + // otherwise CommandManager will think IDE doesn't have any window Active/Focused and think + // user switched to another app and DebugValueWindow will closed itself on "FocusOut" event + //Present (); + } + + protected override void Dispose (bool disposing) + { + if (disposing && !disposed) { + //PreviewWindowManager.WindowClosed -= PreviewWindowManager_WindowClosed; + treeView.Resized -= OnTreeViewResized; + treeView.NodePinned -= OnPinStatusChanged; + treeView.StartEditing -= OnStartEditing; + treeView.EndEditing -= OnEndEditing; + heightConstraint.Dispose (); + widthConstraint.Dispose (); + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj index 924762a652..d38a58406b 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj @@ -189,7 +189,23 @@ + + + + + + + + + + + + + + + + @@ -380,6 +396,8 @@ + + diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugValueWindow.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugValueWindow.cs index d4ad47e7f5..934e39d2c2 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugValueWindow.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugValueWindow.cs @@ -25,6 +25,8 @@ // // +// Note: This is only used by the old (Gtk) TextEditor. + using System; using Gdk; @@ -38,9 +40,10 @@ using MonoDevelop.Components; namespace MonoDevelop.Debugger { + [Obsolete ("This API is only used by the old Gtk TextEditor")] class DebugValueWindow : PopoverWindow { - readonly bool useNewTreeView = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", false); + readonly bool useNewTreeView = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", true); readonly ObjectValueTreeViewController controller; readonly ObjectValueTreeView objValueTreeView; readonly TreeView treeView; @@ -78,7 +81,7 @@ namespace MonoDevelop.Debugger currentBgColor = bgColor; } - public DebugValueWindow (Gtk.Window transientFor, string pinnedWatchFileName, int pinnedWatchLine, StackFrame frame, ObjectValue value, PinnedWatch watch) : base (Gtk.WindowType.Toplevel) + public DebugValueWindow (Gtk.Window transientFor, PinnedWatchLocation location, StackFrame frame, ObjectValue value, PinnedWatch watch) : base (Gtk.WindowType.Toplevel) { TypeHint = WindowTypeHint.PopupMenu; AllowShrink = false; @@ -89,9 +92,10 @@ namespace MonoDevelop.Debugger // Avoid getting the focus when the window is shown. We'll get it when the mouse enters the window AcceptFocus = false; - sw = new ScrolledWindow (); - sw.HscrollbarPolicy = PolicyType.Never; - sw.VscrollbarPolicy = PolicyType.Never; + sw = new ScrolledWindow { + HscrollbarPolicy = PolicyType.Never, + VscrollbarPolicy = PolicyType.Never + }; UpdateTreeStyle (Theme.BackgroundColor); @@ -99,12 +103,10 @@ namespace MonoDevelop.Debugger controller = new ObjectValueTreeViewController (); controller.SetStackFrame (frame); controller.AllowEditing = true; - - treeView = (TreeView) controller.GetControl (headersVisible: false, allowPinning: true, compactView: true, rootPinVisible: true); - controller.PinnedWatch = watch; - controller.PinnedWatchLine = pinnedWatchLine; - controller.PinnedWatchFile = pinnedWatchFileName; + controller.PinnedWatchLocation = location; + + treeView = controller.GetGtkControl (headersVisible: false, allowPinning: true, compactView: true, rootPinVisible: true); if (treeView is IObjectValueTreeView ovtv) { ovtv.StartEditing += OnStartEditing; @@ -121,8 +123,7 @@ namespace MonoDevelop.Debugger objValueTreeView.AllowPinning = true; objValueTreeView.CompactView = true; objValueTreeView.PinnedWatch = watch; - objValueTreeView.PinnedWatchLine = pinnedWatchLine; - objValueTreeView.PinnedWatchFile = pinnedWatchFileName; + objValueTreeView.PinnedWatchLocation = location; objValueTreeView.Frame = frame; objValueTreeView.AddValue (value); @@ -204,8 +205,8 @@ namespace MonoDevelop.Debugger GetPosition (out x, out y); h = (int)sw.Vadjustment.Upper; w = (int)sw.Hadjustment.Upper; - int dy = y + h - this.Screen.Height; - int dx = x + w - this.Screen.Width; + int dy = y + h - Screen.Height; + int dx = x + w - Screen.Width; if (dy > 0 && sw.VscrollbarPolicy == PolicyType.Never) { sw.VscrollbarPolicy = PolicyType.Always; @@ -226,17 +227,17 @@ namespace MonoDevelop.Debugger QueueDraw (); } - protected override void OnSizeAllocated (Gdk.Rectangle allocation) + protected override void OnSizeAllocated (Rectangle allocation) { - if (MonoDevelop.Core.Platform.IsMac || MonoDevelop.Core.Platform.IsWindows) { + if (Platform.IsMac || Platform.IsWindows) { // fails on linux see: Bug 8481 - Debug value tooltips very often appear at the top-left corner of the screen instead of near the element to inspect const int edgeGap = 2; int oldY, x, y; - this.GetPosition (out x, out y); + GetPosition (out x, out y); oldY = y; - Xwt.Rectangle geometry = IdeServices.DesktopService.GetUsableMonitorGeometry (Screen.Number, Screen.GetMonitorAtPoint (x, y)); + var geometry = IdeServices.DesktopService.GetUsableMonitorGeometry (Screen.Number, Screen.GetMonitorAtPoint (x, y)); int top = (int)geometry.Top; if (allocation.Height <= geometry.Height && y + allocation.Height >= geometry.Y + geometry.Height - edgeGap) y = top + ((int)geometry.Height - allocation.Height - edgeGap); @@ -257,7 +258,7 @@ namespace MonoDevelop.Debugger // When Preview window is closed we want to put focus(IsActive=true) back on DebugValueWindow // otherwise CommandManager will think IDE doesn't have any window Active/Focused and think // user switched to another app and DebugValueWindow will closed itself on "FocusOut" event - this.Present (); + Present (); } } } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggerOptionsPanelWidget.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggerOptionsPanelWidget.cs index 466c799857..04da2882de 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggerOptionsPanelWidget.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggerOptionsPanelWidget.cs @@ -183,7 +183,7 @@ namespace MonoDevelop.Debugger checkAllowToString.Sensitive = checkAllowEval.Active; spinTimeout.Value = options.EvaluationOptions.EvaluationTimeout; enableLogging.Active = PropertyService.Get ("MonoDevelop.Debugger.DebuggingService.DebuggerLogging", false); - useNewTreeView.Active = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", false); + useNewTreeView.Active = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", true); } public void Store () diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs index bc64aa7348..220fbc011c 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs @@ -48,7 +48,7 @@ namespace MonoDevelop.Debugger static readonly Xwt.Drawing.Image WarningIconPixbufInner = Xwt.Drawing.Image.FromResource ("exception-outline-16.png"); readonly Dictionary reverseInnerExceptions = new Dictionary (); - readonly bool useNewTreeView = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", false); + readonly bool useNewTreeView = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", true); readonly ExceptionCaughtMessage message; readonly ExceptionInfo exception; @@ -186,7 +186,7 @@ widget ""*.exception_help_link_label"" style ""exception-help-link-label"" controller.SetStackFrame (DebuggingService.CurrentFrame); controller.AllowExpanding = true; - exceptionValueTreeView = (TreeView) controller.GetControl (allowPopupMenu: false); + exceptionValueTreeView = controller.GetGtkControl (allowPopupMenu: false); } else { var objValueTreeView = new ObjectValueTreeView (); objValueTreeView.Frame = DebuggingService.CurrentFrame; diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/LocalsPad.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/LocalsPad.cs index 881a590921..f979b5c29c 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/LocalsPad.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/LocalsPad.cs @@ -25,8 +25,6 @@ // // -using System.Linq; - using Mono.Debugging.Client; namespace MonoDevelop.Debugger @@ -43,6 +41,27 @@ namespace MonoDevelop.Debugger } } +#if ADD_FAKE_NODES + void AddFakeNodes () + { + var xx = new System.Collections.Generic.List (); + + xx.Add (new FakeObjectValueNode ("f1")); + xx.Add (new FakeIsImplicitNotSupportedObjectValueNode ()); + + xx.Add (new FakeEvaluatingGroupObjectValueNode (1)); + xx.Add (new FakeEvaluatingGroupObjectValueNode (0)); + xx.Add (new FakeEvaluatingGroupObjectValueNode (5)); + + xx.Add (new FakeEvaluatingObjectValueNode ()); + xx.Add (new FakeEnumerableObjectValueNode (10)); + xx.Add (new FakeEnumerableObjectValueNode (20)); + xx.Add (new FakeEnumerableObjectValueNode (23)); + + controller.AddValues (xx); + } +#endif + void ReloadValues () { var frame = DebuggingService.CurrentFrame; @@ -61,21 +80,9 @@ namespace MonoDevelop.Debugger controller.ClearValues (); controller.AddValues (locals); - //var xx = new System.Collections.Generic.List (); - - //xx.Add (new FakeObjectValueNode ("f1")); - //xx.Add (new FakeIsImplicitNotSupportedObjectValueNode ()); - - //xx.Add (new FakeEvaluatingGroupObjectValueNode (1)); - //xx.Add (new FakeEvaluatingGroupObjectValueNode (0)); - //xx.Add (new FakeEvaluatingGroupObjectValueNode (5)); - - //xx.Add (new FakeEvaluatingObjectValueNode ()); - //xx.Add (new FakeEnumerableObjectValueNode (10)); - //xx.Add (new FakeEnumerableObjectValueNode (20)); - //xx.Add (new FakeEnumerableObjectValueNode (23)); - - //controller.AddValues (xx); +#if ADD_FAKE_NODES + AddFakeNodes (); +#endif } else { tree.ClearValues (); tree.AddValues (locals); diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/AddNewExpressionObjectValueNode.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/AddNewExpressionObjectValueNode.cs new file mode 100644 index 0000000000..981553c2f3 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/AddNewExpressionObjectValueNode.cs @@ -0,0 +1,35 @@ +// +// AddNewExpressionObjectValueNode.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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. + +namespace MonoDevelop.Debugger +{ + sealed class AddNewExpressionObjectValueNode : ObjectValueNode + { + public AddNewExpressionObjectValueNode () : base (string.Empty) + { + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/DebuggerObjectValueNode.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/DebuggerObjectValueNode.cs index d03cbea270..34f1106645 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/DebuggerObjectValueNode.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/DebuggerObjectValueNode.cs @@ -62,7 +62,7 @@ namespace MonoDevelop.Debugger var name = node.Name; while (node != null && node.Parent is DebuggerObjectValueNode) { expression = node.DebuggerObject.ChildSelector + expression; - node = (DebuggerObjectValueNode)node.Parent; + node = (DebuggerObjectValueNode) node.Parent; name = node.Name; } @@ -107,7 +107,7 @@ namespace MonoDevelop.Debugger #region IEvaluatingGroupObjectValueNode bool IEvaluatingGroupObjectValueNode.IsEvaluatingGroup => DebuggerObject.IsEvaluatingGroup; - ObjectValueNode [] IEvaluatingGroupObjectValueNode.GetEvaluationGroupReplacementNodes () + ObjectValueNode[] IEvaluatingGroupObjectValueNode.GetEvaluationGroupReplacementNodes () { var replacementNodes = new ObjectValueNode[DebuggerObject.ArrayCount]; @@ -157,7 +157,7 @@ namespace MonoDevelop.Debugger }, cancellationToken); } - static Task GetChildrenAsync (ObjectValue value, int index, int count, CancellationToken cancellationToken) + static Task GetChildrenAsync (ObjectValue value, int index, int count, CancellationToken cancellationToken) { return Task.Run(() => { try { diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Gtk/GtkObjectValueTreeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Gtk/GtkObjectValueTreeView.cs index 49338b09f1..20b9cd981e 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Gtk/GtkObjectValueTreeView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Gtk/GtkObjectValueTreeView.cs @@ -102,15 +102,14 @@ namespace MonoDevelop.Debugger bool editing; bool allowEditing; - bool allowWatchExpressions; bool wasHandled; CodeCompletionContext ctx; Gdk.Key key; char keyChar; Gdk.ModifierType modifierState; uint keyValue; - PreviewButtonIcons iconBeforeSelected; - PreviewButtonIcons currentIcon; + PreviewButtonIcon iconBeforeSelected; + PreviewButtonIcon currentIcon; TreeIter currentHoverIter = TreeIter.Zero; Adjustment oldHadjustment; Adjustment oldVadjustment; @@ -173,7 +172,6 @@ namespace MonoDevelop.Debugger ObjectValueTreeViewController controller, bool allowEditing, bool headersVisible, - bool allowWatchExpressions, bool compactView, bool allowPinning, bool allowPopupMenu, @@ -186,10 +184,10 @@ namespace MonoDevelop.Debugger // ensure this is set when we set up the view, don't try and refresh just yet this.allowEditing = allowEditing; - this.allowWatchExpressions = allowWatchExpressions; this.debuggerService = debuggerService; this.controller = controller; + this.root = controller.Root; store = new TreeStore (typeof (string), typeof (string), typeof (string), typeof (bool), typeof (bool), typeof (string), typeof (string), typeof (string), typeof (bool), typeof (string), typeof (Xwt.Drawing.Image), typeof (bool), typeof (string), typeof (Xwt.Drawing.Image), typeof (bool), typeof (string), typeof (ObjectValueNode)); Model = store; @@ -318,6 +316,7 @@ namespace MonoDevelop.Debugger focus_line_width = (int)StyleGetProperty ("focus-line-width") * 2; //we just use *2 version in GetMaxWidth AdjustColumnSizes (); + Refresh (false); } /// @@ -342,13 +341,7 @@ namespace MonoDevelop.Debugger /// Gets a value indicating whether the user should be able to add watch expressions to the tree /// public bool AllowWatchExpressions { - get => allowWatchExpressions; - set { - if (allowWatchExpressions != value) { - allowWatchExpressions = value; - Refresh (false); - } - } + get { return controller.AllowWatchExpressions; } } /// @@ -491,8 +484,7 @@ namespace MonoDevelop.Debugger { base.OnShown (); AdjustColumnSizes (); - if (compactView) - RecalculateWidth (); + CompactColumns (); } protected override void OnRealized () @@ -522,12 +514,28 @@ namespace MonoDevelop.Debugger } /// - /// Reloads the tree from the root node + /// Notifies the treeview that the tree has been cleared + /// + void IObjectValueTreeView.Cleared () + { + Refresh (false); + } + + /// + /// Notifies the treeview that the specified node has been added to the root node's children + /// + /// The node that was appended. + void IObjectValueTreeView.Appended (ObjectValueNode node) + { + Refresh (false); + } + + /// + /// Notifies the treeview that the specified nodes have been added to the root node's children /// - public void Reload (ObjectValueNode root) + /// The nodes that were appended. + void IObjectValueTreeView.Appended (IList nodes) { - // TODO: how to tell whether to reset scroll position or not? - this.root = root; Refresh (false); } @@ -544,7 +552,7 @@ namespace MonoDevelop.Debugger /// the set of replacement nodes. Handles the case where, for example, the "locals" is replaced /// with the set of local values /// - public void LoadEvaluatedNode (ObjectValueNode node, ObjectValueNode [] replacementNodes) + public void LoadEvaluatedNode (ObjectValueNode node, ObjectValueNode[] replacementNodes) { OnEvaluationCompleted (node, replacementNodes); } @@ -561,15 +569,13 @@ namespace MonoDevelop.Debugger // them in so that the tree does not collapse the row when the last child is removed MergeChildrenIntoTree (node, iter, index, count); - // if we did not load all the children, add a More node + // if we did not load all the children, add a Show More node if (!node.ChildrenLoaded) { AppendNodeToTreeModel (iter, null, new ShowMoreValuesObjectValueNode (node)); } } - if (compactView) { - RecalculateWidth (); - } + CompactColumns (); } // TODO: if we don't want the scrolling, we can probably get rid of this @@ -589,8 +595,7 @@ namespace MonoDevelop.Debugger ExpandRow (path, false); } - if (compactView) - RecalculateWidth (); + CompactColumns (); // TODO: all this scrolling kind of seems awkward //if (path != null) @@ -649,8 +654,8 @@ namespace MonoDevelop.Debugger if (replacementNodes.Length == 0) { // we can remove the node altogether, eg there are no local variables to show Remove (ref iter); - } else if (replacementNodes.Length > 0) { - node = replacementNodes [0]; + } else { + node = replacementNodes[0]; SetValues (parent, iter, node.Name, node); for (int n = 1; n < replacementNodes.Length; n++) { @@ -660,9 +665,7 @@ namespace MonoDevelop.Debugger } } - if (compactView) { - RecalculateWidth (); - } + CompactColumns (); } bool Remove (ref TreeIter iter) @@ -697,12 +700,6 @@ namespace MonoDevelop.Debugger restoringState = false; } - //public void Update () - //{ - // //cachedValues.Clear (); - // Refresh (true); - //} - void Refresh (bool resetScrollPosition) { // Note: this is a hack that ideally we could get rid of... @@ -849,7 +846,7 @@ namespace MonoDevelop.Debugger if (val.CanRefresh) valueButton = GettextCatalog.GetString ("Show Value"); } else if (val.IsEvaluating) { - strval = GettextCatalog.GetString ("Evaluating..."); + strval = GettextCatalog.GetString ("Evaluating\u2026"); evaluateStatusIcon = "md-spinner-16"; @@ -956,8 +953,7 @@ namespace MonoDevelop.Debugger base.OnRowExpanded (iter, path); - if (compactView) - RecalculateWidth (); + CompactColumns (); HideValueButton (iter); @@ -970,8 +966,7 @@ namespace MonoDevelop.Debugger base.OnRowCollapsed (iter, path); - if (compactView) - RecalculateWidth (); + CompactColumns (); NodeCollapse?.Invoke (this, new ObjectValueNodeEventArgs (node)); @@ -1140,16 +1135,6 @@ namespace MonoDevelop.Debugger } } - enum PreviewButtonIcons - { - None, - Hidden, - RowHover, - Hover, - Active, - Selected, - } - bool ValidObjectForPreviewIcon (TreeIter it) { var obj = GetDebuggerObjectValueAtIter (it); @@ -1193,13 +1178,13 @@ namespace MonoDevelop.Debugger var iconXOffset = cellArea.X + w + cr.Xpad * 3; if (iconXOffset < evnt.X && iconXOffset + 16 > evnt.X) { - SetPreviewButtonIcon (PreviewButtonIcons.Hover, it); + SetPreviewButtonIcon (PreviewButtonIcon.Hover, it); } else { - SetPreviewButtonIcon (PreviewButtonIcons.RowHover, it); + SetPreviewButtonIcon (PreviewButtonIcon.RowHover, it); } } } else { - SetPreviewButtonIcon (PreviewButtonIcons.RowHover, it); + SetPreviewButtonIcon (PreviewButtonIcon.RowHover, it); } if (allowPinning) { @@ -1214,7 +1199,7 @@ namespace MonoDevelop.Debugger } } } else { - SetPreviewButtonIcon (PreviewButtonIcons.Hidden); + SetPreviewButtonIcon (PreviewButtonIcon.Hidden); } return base.OnMotionNotifyEvent (evnt); } @@ -1231,7 +1216,7 @@ namespace MonoDevelop.Debugger { if (!editing) CleanPinIcon (); - SetPreviewButtonIcon (PreviewButtonIcons.Hidden); + SetPreviewButtonIcon (PreviewButtonIcon.Hidden); return base.OnLeaveNotifyEvent (evnt); } @@ -1283,8 +1268,8 @@ namespace MonoDevelop.Debugger case Gdk.Key.Delete: case Gdk.Key.KP_Delete: case Gdk.Key.BackSpace: - string expression; - ObjectValue val; + //string expression; + //ObjectValue val; TreeIter iter; if (!AllowEditing || !AllowWatchExpressions) @@ -1332,14 +1317,14 @@ namespace MonoDevelop.Debugger { allowStoreColumnSizes = true; + bool closePreviewWindow = true; + bool clickProcessed = false; TreeViewColumn col; CellRenderer cr; TreePath path; - bool closePreviewWindow = true; - bool clickProcessed = false; TreeIter it; - if (this.debuggerService.CanQueryDebugger && evnt.Button == 1 && GetCellAtPos ((int)evnt.X, (int)evnt.Y, out path, out col, out cr) && store.GetIter (out it, path)) { + if (debuggerService.CanQueryDebugger && evnt.Button == 1 && GetCellAtPos ((int)evnt.X, (int)evnt.Y, out path, out col, out cr) && store.GetIter (out it, path)) { if (cr == crpViewer) { clickProcessed = true; var node = GetNodeAtIter (it); @@ -1370,9 +1355,9 @@ namespace MonoDevelop.Debugger startPreviewCaret.X + 16 > evnt.X) { clickProcessed = true; if (compactView) { - SetPreviewButtonIcon (PreviewButtonIcons.Active, it); + SetPreviewButtonIcon (PreviewButtonIcon.Active, it); } else { - SetPreviewButtonIcon (PreviewButtonIcons.Selected, it); + SetPreviewButtonIcon (PreviewButtonIcon.Selected, it); } DebuggingService.ShowPreviewVisualizer (val, this, startPreviewCaret); closePreviewWindow = false; @@ -1567,14 +1552,14 @@ namespace MonoDevelop.Debugger return; } - TreePath [] sel = Selection.GetSelectedRows (); - if (sel.Length == 0) { + var selectedRows = Selection.GetSelectedRows (); + if (selectedRows.Length == 0) { cinfo.Enabled = false; return; } - foreach (TreePath tp in sel) { - if (tp.Depth > 1) { + foreach (var row in selectedRows) { + if (row.Depth > 1) { cinfo.Enabled = false; return; } @@ -1886,29 +1871,33 @@ namespace MonoDevelop.Debugger return columnWidth; } - void RecalculateWidth () + void CompactColumns () { + if (!compactView) + return; + if (!Model.GetIterFirst (out TreeIter iter)) return; - foreach (var column in new [] { expCol, valueCol }) {//No need to calculate for Type and PinIcon columns - // +1 is here because apperently when we calculate MaxWidth and set to FixedWidth - // later GTK when cacluate needed width for Label it doesn't have enough space - // and puts "..." to end of text thinking there is not enough space - // I assume this is because rounding(floating point) calculation errors - // hence do +1 and avoid such problems. + foreach (var column in new [] { expCol, valueCol }) { + // No need to calculate for Type and PinIcon columns + // +1 is here because apperently when we calculate MaxWidth and set to FixedWidth + // later GTK when cacluate needed width for Label it doesn't have enough space + // and puts "..." to end of text thinking there is not enough space + // I assume this is because rounding(floating point) calculation errors + // hence do +1 and avoid such problems. column.FixedWidth = GetMaxWidth (column, iter) + 1; } } - void SetPreviewButtonIcon (PreviewButtonIcons icon, TreeIter it = default (TreeIter)) + void SetPreviewButtonIcon (PreviewButtonIcon icon, TreeIter it = default (TreeIter)) { if (PreviewWindowManager.IsVisible || editing) { return; } if (!it.Equals (TreeIter.Zero)) { if (!ValidObjectForPreviewIcon (it)) { - icon = PreviewButtonIcons.None; + icon = PreviewButtonIcon.None; } } if (!currentHoverIter.Equals (it)) { @@ -1920,52 +1909,30 @@ namespace MonoDevelop.Debugger } } if (!it.Equals (TreeIter.Zero) && store.IterIsValid (it)) { - if (icon == PreviewButtonIcons.Selected) { - if ((currentIcon == PreviewButtonIcons.Active || - currentIcon == PreviewButtonIcons.Hover || - currentIcon == PreviewButtonIcons.RowHover) && it.Equals (TreeIter.Zero)) { + if (icon == PreviewButtonIcon.Selected) { + if ((currentIcon == PreviewButtonIcon.Active || + currentIcon == PreviewButtonIcon.Hover || + currentIcon == PreviewButtonIcon.RowHover) && it.Equals (TreeIter.Zero)) { iconBeforeSelected = currentIcon; } - } else if (icon == PreviewButtonIcons.Active || - icon == PreviewButtonIcons.Hover || - icon == PreviewButtonIcons.RowHover) { + } else if (icon == PreviewButtonIcon.Active || + icon == PreviewButtonIcon.Hover || + icon == PreviewButtonIcon.RowHover) { iconBeforeSelected = icon; if (Selection.IterIsSelected (it)) { - icon = PreviewButtonIcons.Selected; + icon = PreviewButtonIcon.Selected; } } - switch (icon) { - case PreviewButtonIcons.None: - if (store.GetValue (it, PreviewIconColumn) != null) - store.SetValue (it, PreviewIconColumn, null); - break; - case PreviewButtonIcons.Hidden: - if ((string) store.GetValue (it, PreviewIconColumn) != "md-empty") - store.SetValue (it, PreviewIconColumn, "md-empty"); - break; - case PreviewButtonIcons.RowHover: - if ((string) store.GetValue (it, PreviewIconColumn) != "md-preview-normal") - store.SetValue (it, PreviewIconColumn, "md-preview-normal"); - break; - case PreviewButtonIcons.Hover: - if ((string) store.GetValue (it, PreviewIconColumn) != "md-preview-hover") - store.SetValue (it, PreviewIconColumn, "md-preview-hover"); - break; - case PreviewButtonIcons.Active: - if ((string) store.GetValue (it, PreviewIconColumn) != "md-preview-active") - store.SetValue (it, PreviewIconColumn, "md-preview-active"); - break; - case PreviewButtonIcons.Selected: - if ((string) store.GetValue (it, PreviewIconColumn) != "md-preview-selected") { - store.SetValue (it, PreviewIconColumn, "md-preview-selected"); - } - break; - } + var name = ObjectValueTreeViewController.GetPreviewButtonIcon (icon); + var currentName = (string) store.GetValue (it, PreviewIconColumn); + if (currentName != name) + store.SetValue (it, PreviewIconColumn, name); + currentIcon = icon; currentHoverIter = it; } else { - currentIcon = PreviewButtonIcons.None; + currentIcon = PreviewButtonIcon.None; currentHoverIter = TreeIter.Zero; } } @@ -1974,7 +1941,7 @@ namespace MonoDevelop.Debugger { if (!currentHoverIter.Equals (TreeIter.Zero) && store.IterIsValid (currentHoverIter)) { if (Selection.IterIsSelected (currentHoverIter)) { - SetPreviewButtonIcon (PreviewButtonIcons.Selected, currentHoverIter); + SetPreviewButtonIcon (PreviewButtonIcon.Selected, currentHoverIter); } else { SetPreviewButtonIcon (iconBeforeSelected, currentHoverIter); } @@ -2024,7 +1991,7 @@ namespace MonoDevelop.Debugger void HandlePreviewWindowClosed (object sender, EventArgs e) { - SetPreviewButtonIcon (PreviewButtonIcons.Hidden); + SetPreviewButtonIcon (PreviewButtonIcon.Hidden); } void HandleCompletionWindowClosed (object sender, EventArgs e) @@ -2330,6 +2297,5 @@ namespace MonoDevelop.Debugger return false; } #endregion - } } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/IObjectValueTreeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/IObjectValueTreeView.cs index 2bc96db71c..b297f786d7 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/IObjectValueTreeView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/IObjectValueTreeView.cs @@ -25,6 +25,7 @@ // THE SOFTWARE. using System; +using System.Collections.Generic; namespace MonoDevelop.Debugger { @@ -43,11 +44,6 @@ namespace MonoDevelop.Debugger /// bool AllowExpanding { get; set; } - /// - /// Gets or sets a value indicating whether the user should be able to add watch expressions to the tree - /// - bool AllowWatchExpressions { get; set; } - /// /// Gets or sets the pinned watch for the view. When a watch is pinned, the view should display only this value /// @@ -59,9 +55,21 @@ namespace MonoDevelop.Debugger int PinnedWatchOffset { get; } /// - /// Reloads the tree from the root node + /// Notifies the treeview that the tree has been cleared + /// + void Cleared (); + + /// + /// Notifies the treeview that the specified node has been appended + /// + /// The appended node. + void Appended (ObjectValueNode node); + + /// + /// Notifies the treeview that the specified nodes have been appended /// - void Reload (ObjectValueNode root); + /// The appended nodes. + void Appended (IList nodes); /// /// Informs the view to load the children of the given node. startIndex and count may specify a range of @@ -74,7 +82,7 @@ namespace MonoDevelop.Debugger /// the set of replacement nodes. Handles the case where, for example, the "locals" is replaced /// with the set of local values /// - void LoadEvaluatedNode (ObjectValueNode node, ObjectValueNode [] replacementNodes); + void LoadEvaluatedNode (ObjectValueNode node, ObjectValueNode[] replacementNodes); /// /// Triggered when the view tries to expand a node. This may trigger a load of diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/LoadingObjectValueNode.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/LoadingObjectValueNode.cs new file mode 100644 index 0000000000..9cfe3aae44 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/LoadingObjectValueNode.cs @@ -0,0 +1,38 @@ +// +// LoadingObjectValueNode.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 MonoDevelop.Core; + +namespace MonoDevelop.Debugger +{ + sealed class LoadingObjectValueNode : ObjectValueNode + { + public LoadingObjectValueNode (ObjectValueNode parent) : base (GettextCatalog.GetString ("Loading\u2026")) + { + Parent = parent; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectCellViewBase.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectCellViewBase.cs new file mode 100644 index 0000000000..3d5413df6b --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectCellViewBase.cs @@ -0,0 +1,204 @@ +// +// MacDebuggerObjectCellViewBase.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 AppKit; +using Foundation; +using CoreGraphics; + +using Xwt.Drawing; + +using MonoDevelop.Ide; +using MonoDevelop.Components; + +namespace MonoDevelop.Debugger +{ + abstract class MacDebuggerObjectCellViewBase : NSTableCellView + { + protected const int CompactImageSize = 12; + protected const int RowCellSpacing = 2; + protected const int ImageSize = 16; + protected const int MarginSize = 2; + + protected MacDebuggerObjectCellViewBase (MacObjectValueTreeView treeView, string identifier) + { + Identifier = identifier; + TreeView = treeView; + } + + protected MacDebuggerObjectCellViewBase (IntPtr handle) : base (handle) + { + } + + protected MacObjectValueTreeView TreeView { + get; private set; + } + + public override NSObject ObjectValue { + get { return base.ObjectValue; } + set { + var target = ((MacObjectValueNode)value)?.Target; + + if (Node != target) { + if (target != null) + target.ValueChanged += OnValueChanged; + + if (Node != null) + Node.ValueChanged -= OnValueChanged; + + Node = target; + } + + base.ObjectValue = value; + + if (Superview != null) + UpdateContents (); + } + } + + public nfloat OptimalWidth { + get; protected set; + } + + public ObjectValueNode Node { + get; private set; + } + + public nint Row { + get; set; + } + + public bool IsShowMoreValues { + get { return Node is ShowMoreValuesObjectValueNode; } + } + + public bool IsLoading { + get { return Node is LoadingObjectValueNode; } + } + + protected static NSImage GetImage (string name, Gtk.IconSize size) + { + var icon = ImageService.GetIcon (name, size); + + try { + return icon.ToNSImage (); + } catch (Exception ex) { + Core.LoggingService.LogError ($"Failed to load '{name}' as an NSImage", ex); + return icon.ToBitmap (NSScreen.MainScreen.BackingScaleFactor).ToNSImage (); + } + } + + protected static NSImage GetImage (string name, Gtk.IconSize size, double alpha) + { + var icon = ImageService.GetIcon (name, size).WithAlpha (alpha); + + try { + return icon.ToNSImage (); + } catch (Exception ex) { + Core.LoggingService.LogError ($"Failed to load '{name}' as an NSImage", ex); + return icon.ToBitmap (NSScreen.MainScreen.BackingScaleFactor).ToNSImage (); + } + } + + protected static NSImage GetImage (string name, int width, int height) + { + var icon = ImageService.GetIcon (name).WithSize (width, height); + + try { + return icon.ToNSImage (); + } catch (Exception ex) { + Core.LoggingService.LogError ($"Failed to load '{name}' as an NSImage", ex); + return icon.ToBitmap (NSScreen.MainScreen.BackingScaleFactor).ToNSImage (); + } + } + + protected static CGColor GetCGColor (Color color) + { + return new CGColor ((nfloat) color.Red, (nfloat) color.Green, (nfloat) color.Blue); + } + + protected static NSAttributedString GetAttributedString (string text, bool center = false) + { + var paragraphStyle = center ? new NSMutableParagraphStyle { Alignment = NSTextAlignment.Center } : null; + + return new NSAttributedString (text ?? string.Empty, baselineOffset: 1, paragraphStyle: paragraphStyle); + } + + protected static NSAttributedString GetAttributedPlaceholderString (string text) + { + return new NSAttributedString (text ?? string.Empty, baselineOffset: 1, strokeColor: NSColor.PlaceholderTextColor); + } + + protected void UpdateFont (NSControl control, int sizeDelta = 0) + { + var font = TreeView.CustomFont ?? TreeView.Font; + + if (sizeDelta != 0) { + control.Font = NSFont.FromDescription (font.FontDescriptor, font.PointSize + sizeDelta); + } else { + control.Font = font; + } + } + + public override void ViewDidMoveToSuperview () + { + base.ViewDidMoveToSuperview (); + UpdateContents (); + } + + public override NSBackgroundStyle BackgroundStyle { + get { return base.BackgroundStyle; } + set { + base.BackgroundStyle = value; + UpdateContents (); + } + } + + protected abstract void UpdateContents (); + + protected void Refresh () + { + UpdateContents (); + SetNeedsDisplayInRect (Frame); + } + + void OnValueChanged (object sender, EventArgs e) + { + Refresh (); + } + + protected override void Dispose (bool disposing) + { + if (disposing && Node != null) { + Node.ValueChanged -= OnValueChanged; + Node = null; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectNameView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectNameView.cs new file mode 100644 index 0000000000..65dfc8bd56 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectNameView.cs @@ -0,0 +1,288 @@ +// +// MacDebuggerObjectNameView.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Collections.Generic; + +using AppKit; +using Foundation; + +using MonoDevelop.Core; +using MonoDevelop.Ide; + +using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; + +namespace MonoDevelop.Debugger +{ + /// + /// The NSTableViewCell used for the "Name" column. + /// + class MacDebuggerObjectNameView : MacDebuggerObjectCellViewBase + { + class EditableTextField : NSTextField + { + readonly MacDebuggerObjectNameView nameView; + string oldValue, newValue; + bool editing; + + public EditableTextField (MacDebuggerObjectNameView nameView) + { + this.nameView = nameView; + } + + public override void DidBeginEditing (NSNotification notification) + { + base.DidBeginEditing (notification); + nameView.TreeView.OnStartEditing (); + oldValue = newValue = StringValue.Trim (); + editing = true; + } + + public override void DidChange (NSNotification notification) + { + newValue = StringValue.Trim (); + base.DidChange (notification); + } + + public override void DidEndEditing (NSNotification notification) + { + base.DidEndEditing (notification); + + if (!editing) + return; + + editing = false; + + nameView.TreeView.OnEndEditing (); + + if (nameView.Node is AddNewExpressionObjectValueNode) { + if (newValue.Length > 0) + nameView.TreeView.OnExpressionAdded (newValue); + } else if (newValue != oldValue) { + nameView.TreeView.OnExpressionEdited (nameView.Node, newValue); + } + + oldValue = newValue = null; + } + + protected override void Dispose (bool disposing) + { + if (disposing) + nameView.Dispose (); + + base.Dispose (disposing); + } + } + + readonly List constraints = new List (); + PreviewButtonIcon currentIcon; + bool previewIconVisible; + bool disposed; + + public MacDebuggerObjectNameView (MacObjectValueTreeView treeView) : base (treeView, "name") + { + ImageView = new NSImageView { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + TextField = new EditableTextField (this) { + AutoresizingMask = NSViewResizingMask.WidthSizable, + TranslatesAutoresizingMaskIntoConstraints = false, + BackgroundColor = NSColor.Clear, + Bordered = false, + Editable = false + }; + TextField.Cell.UsesSingleLineMode = true; + TextField.Cell.Wraps = false; + + AddSubview (ImageView); + AddSubview (TextField); + + PreviewButton = new NSButton { + TranslatesAutoresizingMaskIntoConstraints = false, + Image = GetImage ("md-empty", Gtk.IconSize.Menu), + BezelStyle = NSBezelStyle.Inline, + Bordered = false + }; + PreviewButton.Activated += OnPreviewButtonClicked; + } + + public MacDebuggerObjectNameView (IntPtr handle) : base (handle) + { + } + + public NSButton PreviewButton { + get; private set; + } + + protected override void UpdateContents () + { + if (Node == null) + return; + + foreach (var constraint in constraints) { + constraint.Active = false; + constraint.Dispose (); + } + constraints.Clear (); + + OptimalWidth = MarginSize; + + var iconName = ObjectValueTreeViewController.GetIcon (Node.Flags); + ImageView.Image = GetImage (iconName, Gtk.IconSize.Menu); + constraints.Add (ImageView.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + constraints.Add (ImageView.LeadingAnchor.ConstraintEqualToAnchor (LeadingAnchor, MarginSize)); + constraints.Add (ImageView.WidthAnchor.ConstraintEqualToConstant (ImageSize)); + constraints.Add (ImageView.HeightAnchor.ConstraintEqualToConstant (ImageSize)); + + OptimalWidth += ImageView.Image.Size.Width; + OptimalWidth += RowCellSpacing; + + var editable = TreeView.AllowWatchExpressions && Node.Parent is RootObjectValueNode; + var textColor = NSColor.ControlText; + var placeholder = string.Empty; + var name = Node.Name; + + if (Node.IsUnknown) { + if (TreeView.DebuggerService.Frame != null) + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueDisabledText)); + } else if (Node.IsError || Node.IsNotSupported) { + } else if (Node.IsImplicitNotSupported) { + } else if (Node.IsEvaluating) { + if (Node.GetIsEvaluatingGroup ()) + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueDisabledText)); + } else if (Node.IsEnumerable) { + } else if (Node is AddNewExpressionObjectValueNode) { + placeholder = GettextCatalog.GetString ("Add new expression"); + name = string.Empty; + editable = true; + } else if (TreeView.Controller.GetNodeHasChangedSinceLastCheckpoint (Node)) { + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueModifiedText)); + } + + TextField.PlaceholderAttributedString = GetAttributedPlaceholderString (placeholder); + TextField.AttributedStringValue = GetAttributedString (name); + TextField.TextColor = textColor; + TextField.Editable = editable; + UpdateFont (TextField); + TextField.SizeToFit (); + + OptimalWidth += TextField.Frame.Width; + + constraints.Add (TextField.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + constraints.Add (TextField.LeadingAnchor.ConstraintEqualToAnchor (ImageView.TrailingAnchor, RowCellSpacing)); + + if (MacObjectValueTreeView.ValidObjectForPreviewIcon (Node)) { + SetPreviewButtonIcon (PreviewButtonIcon.Hidden); + + if (!previewIconVisible) { + AddSubview (PreviewButton); + previewIconVisible = true; + } + + constraints.Add (TextField.WidthAnchor.ConstraintGreaterThanOrEqualToConstant (TextField.Frame.Width)); + constraints.Add (PreviewButton.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + constraints.Add (PreviewButton.LeadingAnchor.ConstraintEqualToAnchor (TextField.TrailingAnchor, RowCellSpacing)); + constraints.Add (PreviewButton.WidthAnchor.ConstraintEqualToConstant (ImageSize)); + constraints.Add (PreviewButton.HeightAnchor.ConstraintEqualToConstant (ImageSize)); + + OptimalWidth += RowCellSpacing; + OptimalWidth += PreviewButton.Frame.Width; + } else { + if (previewIconVisible) { + PreviewButton.RemoveFromSuperview (); + previewIconVisible = false; + } + + constraints.Add (TextField.TrailingAnchor.ConstraintEqualToAnchor (TrailingAnchor, -MarginSize)); + } + + foreach (var constraint in constraints) + constraint.Active = true; + + OptimalWidth += MarginSize; + } + + public void SetPreviewButtonIcon (PreviewButtonIcon icon) + { + if (!previewIconVisible || icon == currentIcon) + return; + + var name = ObjectValueTreeViewController.GetPreviewButtonIcon (icon); + PreviewButton.Image = GetImage (name, Gtk.IconSize.Menu); + currentIcon = icon; + + SetNeedsDisplayInRect (PreviewButton.Frame); + } + + void OnPreviewButtonClicked (object sender, EventArgs e) + { + if (!TreeView.DebuggerService.CanQueryDebugger || PreviewWindowManager.IsVisible) + return; + + if (!MacObjectValueTreeView.ValidObjectForPreviewIcon (Node)) + return; + + // convert the buttons frame to window coords + var buttonLocation = PreviewButton.ConvertPointToView (CoreGraphics.CGPoint.Empty, null); + + // now convert the frame to absolute screen coordinates + buttonLocation = PreviewButton.Window.ConvertPointToScreen (buttonLocation); + + var nativeRoot = MacInterop.GtkQuartz.GetWindow (IdeApp.Workbench.RootWindow); + + // convert to root window coordinates + buttonLocation = nativeRoot.ConvertPointFromScreen (buttonLocation); + // the Cocoa Y axis is flipped, convert to Gtk + buttonLocation.Y = nativeRoot.Frame.Height - buttonLocation.Y; + // Gtk coords don't include the toolbar and decorations ofsset, so substract it + buttonLocation.Y -= nativeRoot.Frame.Height - nativeRoot.ContentView.Frame.Height; + + int width = (int) PreviewButton.Frame.Width; + int height = (int) PreviewButton.Frame.Height; + + var buttonArea = new Gdk.Rectangle ((int) buttonLocation.X, (int) buttonLocation.Y, width, height); + var val = Node.GetDebuggerObjectValue (); + + SetPreviewButtonIcon (PreviewButtonIcon.Active); + + DebuggingService.ShowPreviewVisualizer (val, IdeApp.Workbench.RootWindow, buttonArea); + } + + protected override void Dispose (bool disposing) + { + if (disposing && !disposed) { + PreviewButton.Activated -= OnPreviewButtonClicked; + foreach (var constraint in constraints) + constraint.Dispose (); + constraints.Clear (); + disposed = true; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectPinView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectPinView.cs new file mode 100644 index 0000000000..45b31cc810 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectPinView.cs @@ -0,0 +1,155 @@ +// +// MacDebuggerObjectPinView.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 AppKit; + +using MonoDevelop.Core; +using MonoDevelop.Ide; + +namespace MonoDevelop.Debugger +{ + class MacDebuggerObjectPinView : MacDebuggerObjectCellViewBase + { + static readonly NSImage unpinnedImage = GetImage ("md-pin-up", Gtk.IconSize.Menu); + static readonly NSImage pinnedImage = GetImage ("md-pin-down", Gtk.IconSize.Menu); + static readonly NSImage liveUpdateOnImage = GetImage ("md-live", Gtk.IconSize.Menu); + static readonly NSImage liveUpdateOffImage = GetImage ("md-live", Gtk.IconSize.Menu, 0.5); + static readonly NSImage none = GetImage ("md-empty", Gtk.IconSize.Menu); + public const int MinWidth = MarginSize + 16 + MarginSize; + public const int MaxWidth = MarginSize + 16 + RowCellSpacing + 16 + MarginSize; + bool disposed; + bool pinned; + + public MacDebuggerObjectPinView (MacObjectValueTreeView treeView) : base (treeView, "pin") + { + PinButton = new NSButton { + TranslatesAutoresizingMaskIntoConstraints = false, + BezelStyle = NSBezelStyle.Inline, + Image = none, + Bordered = false, + }; + PinButton.AccessibilityTitle = GettextCatalog.GetString ("Pin to the editor"); + PinButton.Activated += OnPinButtonClicked; + AddSubview (PinButton); + + LiveUpdateButton = new NSButton { + TranslatesAutoresizingMaskIntoConstraints = false, + BezelStyle = NSBezelStyle.Inline, + Image = liveUpdateOffImage, + Bordered = false + }; + LiveUpdateButton.AccessibilityTitle = GettextCatalog.GetString ("Refresh value"); + LiveUpdateButton.Activated += OnLiveUpdateButtonClicked; + AddSubview (LiveUpdateButton); + + PinButton.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor).Active = true; + PinButton.LeadingAnchor.ConstraintEqualToAnchor (LeadingAnchor, MarginSize).Active = true; + PinButton.WidthAnchor.ConstraintEqualToConstant (ImageSize).Active = true; + PinButton.HeightAnchor.ConstraintEqualToConstant (ImageSize).Active = true; + + LiveUpdateButton.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor).Active = true; + LiveUpdateButton.LeadingAnchor.ConstraintEqualToAnchor (PinButton.TrailingAnchor, RowCellSpacing).Active = true; + LiveUpdateButton.TrailingAnchor.ConstraintEqualToAnchor (TrailingAnchor, -MarginSize).Active = true; + LiveUpdateButton.WidthAnchor.ConstraintEqualToConstant (ImageSize).Active = true; + LiveUpdateButton.HeightAnchor.ConstraintEqualToConstant (ImageSize).Active = true; + } + + public MacDebuggerObjectPinView (IntPtr handle) : base (handle) + { + OptimalWidth = MaxWidth; + } + + public NSButton PinButton { + get; private set; + } + + public NSButton LiveUpdateButton { + get; private set; + } + + protected override void UpdateContents () + { + if (Node == null) + return; + + if (TreeView.PinnedWatch != null && Node.Parent == TreeView.Controller.Root) { + PinButton.Image = pinnedImage; + pinned = true; + } else { + PinButton.Image = none; + pinned = false; + } + + if (pinned) { + if (TreeView.PinnedWatch.LiveUpdate) + LiveUpdateButton.Image = liveUpdateOnImage; + else + LiveUpdateButton.Image = liveUpdateOffImage; + } else { + LiveUpdateButton.Image = none; + } + } + + void OnPinButtonClicked (object sender, EventArgs e) + { + if (pinned) { + TreeView.Unpin (Node); + } else { + TreeView.Pin (Node); + } + } + + void OnLiveUpdateButtonClicked (object sender, EventArgs e) + { + if (pinned) { + DebuggingService.SetLiveUpdateMode (TreeView.PinnedWatch, !TreeView.PinnedWatch.LiveUpdate); + Refresh (); + } + } + + public void SetMouseHover (bool hover) + { + if (pinned) + return; + + PinButton.Image = hover || IdeServices.DesktopService.AccessibilityInUse ? unpinnedImage : none; + SetNeedsDisplayInRect (PinButton.Frame); + } + + protected override void Dispose (bool disposing) + { + if (disposing && !disposed) { + LiveUpdateButton.Activated -= OnLiveUpdateButtonClicked; + PinButton.Activated -= OnPinButtonClicked; + disposed = true; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectTypeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectTypeView.cs new file mode 100644 index 0000000000..e21e3ca77f --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectTypeView.cs @@ -0,0 +1,70 @@ +// +// MacDebuggerObjectTypeView.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 AppKit; + +namespace MonoDevelop.Debugger +{ + /// + /// The NSTableViewCell used for the "Type" column. + /// + class MacDebuggerObjectTypeView : MacDebuggerObjectCellViewBase + { + public MacDebuggerObjectTypeView (MacObjectValueTreeView treeView) : base (treeView, "type") + { + TextField = new NSTextField { + AutoresizingMask = NSViewResizingMask.WidthSizable, + TranslatesAutoresizingMaskIntoConstraints = false, + BackgroundColor = NSColor.Clear, + Bordered = false, + Editable = false + }; + TextField.Cell.UsesSingleLineMode = true; + TextField.Cell.Wraps = false; + + AddSubview (TextField); + + TextField.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor).Active = true; + TextField.LeadingAnchor.ConstraintEqualToAnchor (LeadingAnchor, MarginSize).Active = true; + TextField.TrailingAnchor.ConstraintEqualToAnchor (TrailingAnchor, -MarginSize).Active = true; + } + + public MacDebuggerObjectTypeView (IntPtr handle) : base (handle) + { + } + + protected override void UpdateContents () + { + TextField.AttributedStringValue = GetAttributedString (Node?.TypeName); + UpdateFont (TextField); + TextField.SizeToFit (); + + OptimalWidth = MarginSize + TextField.Frame.Width + MarginSize; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectValueView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectValueView.cs new file mode 100644 index 0000000000..07e0ed062a --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacDebuggerObjectValueView.cs @@ -0,0 +1,381 @@ +// +// MacDebuggerObjectValueView.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Collections.Generic; + +using AppKit; +using Foundation; +using CoreGraphics; + +using Xwt.Drawing; + +using MonoDevelop.Core; + +namespace MonoDevelop.Debugger +{ + /// + /// The NSTableViewCell used for the "Value" column. + /// + class MacDebuggerObjectValueView : MacDebuggerObjectCellViewBase + { + class EditableTextField : NSTextField + { + readonly MacDebuggerObjectValueView valueView; + string oldValue, newValue; + bool editing; + + public EditableTextField (MacDebuggerObjectValueView valueView) + { + this.valueView = valueView; + } + + public override void DidBeginEditing (NSNotification notification) + { + base.DidBeginEditing (notification); + valueView.TreeView.OnStartEditing (); + oldValue = newValue = StringValue.Trim (); + editing = true; + } + + public override void DidChange (NSNotification notification) + { + newValue = StringValue.Trim (); + base.DidChange (notification); + } + + public override void DidEndEditing (NSNotification notification) + { + base.DidEndEditing (notification); + + if (!editing) + return; + + editing = false; + + valueView.TreeView.OnEndEditing (); + + if (newValue != oldValue && valueView.TreeView.GetEditValue (valueView.Node, newValue)) + valueView.Refresh (); + + oldValue = newValue = null; + } + + protected override void Dispose (bool disposing) + { + if (disposing) + valueView.Dispose (); + + base.Dispose (disposing); + } + } + + readonly List constraints = new List (); + NSImageView statusIcon; + bool statusIconVisible; + NSView colorPreview; + bool colorPreviewVisible; + NSButton valueButton; + bool valueButtonVisible; + NSButton viewerButton; + bool viewerButtonVisible; + bool disposed; + + public MacDebuggerObjectValueView (MacObjectValueTreeView treeView) : base (treeView, "value") + { + statusIcon = new NSImageView { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + colorPreview = new NSView (new CGRect (0, 0, 16, 16)) { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + valueButton = new NSButton { + TranslatesAutoresizingMaskIntoConstraints = false, + Title = GettextCatalog.GetString ("Show More"), + BezelStyle = NSBezelStyle.Inline + }; + valueButton.Cell.UsesSingleLineMode = true; + UpdateFont (valueButton, -3); + valueButton.Activated += OnValueButtonActivated; + + int imageSize = treeView.CompactView ? CompactImageSize : ImageSize; + viewerButton = new NSButton { + Image = GetImage (Gtk.Stock.Edit, imageSize, imageSize), + TranslatesAutoresizingMaskIntoConstraints = false + }; + viewerButton.BezelStyle = NSBezelStyle.Inline; + viewerButton.Bordered = false; + viewerButton.Activated += OnViewerButtonActivated; + + TextField = new EditableTextField (this) { + AutoresizingMask = NSViewResizingMask.WidthSizable, + TranslatesAutoresizingMaskIntoConstraints = false, + BackgroundColor = NSColor.Clear, + Bordered = false, + Editable = false + }; + TextField.Cell.UsesSingleLineMode = true; + TextField.Cell.Wraps = false; + + AddSubview (TextField); + } + + public MacDebuggerObjectValueView (IntPtr handle) : base (handle) + { + } + + protected override void UpdateContents () + { + if (Node == null) + return; + + foreach (var constraint in constraints) { + constraint.Active = false; + constraint.Dispose (); + } + constraints.Clear (); + + var editable = TreeView.GetCanEditNode (Node); + var textColor = NSColor.ControlText; + string evaluateStatusIcon = null; + string valueButtonText = null; + var showViewerButton = false; + Color? previewColor = null; + string strval; + + if (Node.IsUnknown) { + if (TreeView.DebuggerService.Frame != null) { + strval = GettextCatalog.GetString ("The name '{0}' does not exist in the current context.", Node.Name); + } else { + strval = string.Empty; + } + evaluateStatusIcon = Ide.Gui.Stock.Warning; + } else if (Node.IsError || Node.IsNotSupported) { + evaluateStatusIcon = Ide.Gui.Stock.Warning; + strval = Node.Value; + int i = strval.IndexOf ('\n'); + if (i != -1) + strval = strval.Substring (0, i); + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueErrorText)); + } else if (Node.IsImplicitNotSupported) { + strval = "";//val.Value; with new "Show Value" button we don't want to display message "Implicit evaluation is disabled" + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueDisabledText)); + if (Node.CanRefresh) + valueButtonText = GettextCatalog.GetString ("Show Value"); + } else if (Node.IsEvaluating) { + strval = GettextCatalog.GetString ("Evaluating\u2026"); + + evaluateStatusIcon = "md-spinner-16"; + + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueDisabledText)); + } else if (Node.IsEnumerable) { + if (Node is ShowMoreValuesObjectValueNode) { + valueButtonText = GettextCatalog.GetString ("Show More"); + } else { + valueButtonText = GettextCatalog.GetString ("Show Values"); + } + strval = ""; + } else if (Node is AddNewExpressionObjectValueNode) { + strval = string.Empty; + editable = false; + } else { + strval = TreeView.Controller.GetDisplayValueWithVisualisers (Node, out showViewerButton); + + if (TreeView.Controller.GetNodeHasChangedSinceLastCheckpoint (Node)) + textColor = NSColor.FromCGColor (GetCGColor (Styles.ObjectValueTreeValueModifiedText)); + + var val = Node.GetDebuggerObjectValue (); + if (val != null && !val.IsNull && DebuggingService.HasGetConverter (val)) { + try { + previewColor = DebuggingService.GetGetConverter (val).GetValue (val); + } catch { + previewColor = null; + } + } + } + + strval = strval.Replace ("\r\n", " ").Replace ("\n", " "); + + var views = new List (); + + OptimalWidth = MarginSize; + + // First item: Status Icon + if (evaluateStatusIcon != null) { + statusIcon.Image = GetImage (evaluateStatusIcon, Gtk.IconSize.Menu); + + if (!statusIconVisible) { + AddSubview (statusIcon); + statusIconVisible = true; + } + + constraints.Add (statusIcon.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + constraints.Add (statusIcon.WidthAnchor.ConstraintEqualToConstant (ImageSize)); + constraints.Add (statusIcon.HeightAnchor.ConstraintEqualToConstant (ImageSize)); + views.Add (statusIcon); + + OptimalWidth += statusIcon.Image.Size.Width; + OptimalWidth += RowCellSpacing; + } else if (statusIconVisible) { + statusIcon.RemoveFromSuperview (); + statusIconVisible = false; + } + + // Second Item: Color Preview + if (previewColor != null) { + colorPreview.Layer.BackgroundColor = GetCGColor (previewColor.Value); + + if (!colorPreviewVisible) { + AddSubview (colorPreview); + colorPreviewVisible = true; + } + + constraints.Add (colorPreview.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + constraints.Add (colorPreview.WidthAnchor.ConstraintEqualToConstant (ImageSize)); + constraints.Add (colorPreview.HeightAnchor.ConstraintEqualToConstant (ImageSize)); + views.Add (colorPreview); + + OptimalWidth += colorPreview.Frame.Width; + OptimalWidth += RowCellSpacing; + } else if (colorPreviewVisible) { + colorPreview.RemoveFromSuperview (); + colorPreviewVisible = false; + } + + // Third Item: Value Button + if (valueButtonText != null && !((MacObjectValueNode) ObjectValue).HideValueButton) { + valueButton.AttributedTitle = GetAttributedString (valueButtonText, true); + UpdateFont (valueButton, -3); + valueButton.SizeToFit (); + + if (!valueButtonVisible) { + AddSubview (valueButton); + valueButtonVisible = true; + } + + constraints.Add (valueButton.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + views.Add (valueButton); + + OptimalWidth += valueButton.Frame.Width; + OptimalWidth += RowCellSpacing; + } else if (valueButtonVisible) { + valueButton.RemoveFromSuperview (); + valueButtonVisible = false; + } + + // Fourth Item: Viewer Button + if (showViewerButton) { + if (!viewerButtonVisible) { + AddSubview (viewerButton); + viewerButtonVisible = true; + } + + constraints.Add (viewerButton.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + constraints.Add (viewerButton.WidthAnchor.ConstraintEqualToConstant (viewerButton.Image.Size.Width)); + constraints.Add (viewerButton.HeightAnchor.ConstraintEqualToConstant (viewerButton.Image.Size.Height)); + views.Add (viewerButton); + + OptimalWidth += viewerButton.Frame.Width; + OptimalWidth += RowCellSpacing; + } else if (viewerButtonVisible) { + viewerButton.RemoveFromSuperview (); + viewerButtonVisible = false; + } + + // Fifth Item: Text Value + TextField.AttributedStringValue = GetAttributedString (strval); + TextField.TextColor = textColor; + TextField.Editable = editable; + UpdateFont (TextField); + + constraints.Add (TextField.CenterYAnchor.ConstraintEqualToAnchor (CenterYAnchor)); + views.Add (TextField); + + // lay out our views... + var leadingAnchor = LeadingAnchor; + + for (int i = 0; i < views.Count; i++) { + var view = views[i]; + + constraints.Add (view.LeadingAnchor.ConstraintEqualToAnchor (leadingAnchor, i == 0 ? MarginSize : RowCellSpacing)); + leadingAnchor = view.TrailingAnchor; + } + + constraints.Add (TextField.TrailingAnchor.ConstraintEqualToAnchor (TrailingAnchor, -MarginSize)); + + foreach (var constraint in constraints) + constraint.Active = true; + + TextField.SizeToFit (); + + OptimalWidth += TextField.Frame.Width; + OptimalWidth += MarginSize; + } + + void OnValueButtonActivated (object sender, EventArgs e) + { + if (Node.IsEnumerable) { + if (Node is ShowMoreValuesObjectValueNode moreNode) { + TreeView.LoadMoreChildren (moreNode.EnumerableNode); + } else { + // use ExpandItem to expand so we see the loading message, expanding the node will trigger a fetch of the children + TreeView.ExpandItem (ObjectValue, false); + } + } else { + // this is likely to support IsImplicitNotSupported + TreeView.Refresh (Node); + } + + ((MacObjectValueNode) ObjectValue).HideValueButton = true; + Refresh (); + } + + void OnViewerButtonActivated (object sender, EventArgs e) + { + if (!TreeView.DebuggerService.CanQueryDebugger) + return; + + if (TreeView.ShowVisualizer (Node)) + Refresh (); + } + + protected override void Dispose (bool disposing) + { + if (disposing && !disposed) { + viewerButton.Activated -= OnViewerButtonActivated; + valueButton.Activated -= OnValueButtonActivated; + foreach (var constraint in constraints) + constraint.Dispose (); + constraints.Clear (); + disposed = true; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueNode.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueNode.cs new file mode 100644 index 0000000000..12359aee74 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueNode.cs @@ -0,0 +1,50 @@ +// +// MacObjectValueNode.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Collections.Generic; + +using Foundation; + +namespace MonoDevelop.Debugger +{ + /// + /// NSObject wrapper for data items in the Cocoa implementation of the ObjectValueTreeView. + /// + class MacObjectValueNode : NSObject + { + public readonly List Children = new List (); + public readonly MacObjectValueNode Parent; + public readonly ObjectValueNode Target; + public bool HideValueButton; + + public MacObjectValueNode (MacObjectValueNode parent, ObjectValueNode target) + { + Parent = parent; + Target = target; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeView.cs new file mode 100644 index 0000000000..79f6f2741b --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeView.cs @@ -0,0 +1,1030 @@ +// +// MacObjectValueTreeView.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Text; +using System.Collections.Generic; + +using AppKit; +using Foundation; +using CoreGraphics; + +using MonoDevelop.Core; +using MonoDevelop.Ide.Commands; +using MonoDevelop.Components.Commands; + +namespace MonoDevelop.Debugger +{ + public class MacObjectValueTreeView : NSOutlineView, IObjectValueTreeView + { + const int MinimumNameColumnWidth = 48; + const int MinimumValueColumnWidth = 64; + const int MinimumTypeColumnWidth = 48; + + MacObjectValueTreeViewDelegate treeViewDelegate; + MacObjectValueTreeViewDataSource dataSource; + + readonly NSTableColumn nameColumn; + readonly NSTableColumn valueColumn; + readonly NSTableColumn typeColumn; + readonly NSTableColumn pinColumn; + readonly bool allowPopupMenu; + readonly bool rootPinVisible; + readonly bool compactView; + + PinnedWatch pinnedWatch; + + PreviewButtonIcon currentHoverIcon; + nint currentHoverRow = -1; + + double nameColumnWidth = 0.3; + double valueColumnWidth = 0.5; + double typeColumnWidth = 0.2; + + bool allowEditing; + bool autoresizing; + bool disposed; + + public MacObjectValueTreeView ( + IObjectValueDebuggerService debuggerService, + ObjectValueTreeViewController controller, + bool allowEditing, + bool headersVisible, + bool compactView, + bool allowPinning, + bool allowPopupMenu, + bool rootPinVisible) + { + DebuggerService = debuggerService; + Controller = controller; + + this.rootPinVisible = rootPinVisible; + this.allowPopupMenu = allowPopupMenu; + this.allowEditing = allowEditing; + this.compactView = compactView; + + DataSource = dataSource = new MacObjectValueTreeViewDataSource (this, controller.Root, controller.AllowWatchExpressions); + Delegate = treeViewDelegate = new MacObjectValueTreeViewDelegate (this); + ColumnAutoresizingStyle = NSTableViewColumnAutoresizingStyle.None; + treeViewDelegate.SelectionChanged += OnSelectionChanged; + UsesAlternatingRowBackgroundColors = true; + FocusRingType = NSFocusRingType.None; + AutoresizesOutlineColumn = false; + AllowsColumnResizing = !compactView; + + var resizingMask = compactView ? NSTableColumnResizing.None : NSTableColumnResizing.UserResizingMask; + + nameColumn = new NSTableColumn ("name") { Editable = controller.AllowWatchExpressions, MinWidth = MinimumNameColumnWidth, ResizingMask = resizingMask }; + nameColumn.Title = GettextCatalog.GetString ("Name"); + AddColumn (nameColumn); + + OutlineTableColumn = nameColumn; + + valueColumn = new NSTableColumn ("value") { Editable = controller.AllowEditing, MinWidth = MinimumValueColumnWidth, ResizingMask = resizingMask }; + valueColumn.Title = GettextCatalog.GetString ("Value"); + if (compactView) + valueColumn.MaxWidth = 800; + AddColumn (valueColumn); + + if (!compactView) { + typeColumn = new NSTableColumn ("type") { Editable = false, MinWidth = MinimumTypeColumnWidth, ResizingMask = resizingMask }; + typeColumn.Title = GettextCatalog.GetString ("Type"); + AddColumn (typeColumn); + } + + if (allowPinning) { + pinColumn = new NSTableColumn ("pin") { Editable = false, ResizingMask = NSTableColumnResizing.None }; + pinColumn.MinWidth = pinColumn.MaxWidth = pinColumn.Width = MacDebuggerObjectPinView.MinWidth; + AddColumn (pinColumn); + } + + if (headersVisible) { + HeaderView.AlphaValue = 1.0f; + } else { + HeaderView = null; + } + + PreviewWindowManager.WindowClosed += OnPreviewWindowClosed; + + // disable implicit animations + WantsLayer = true; + Layer.Actions = new NSDictionary ( + "actions", NSNull.Null, + "contents", NSNull.Null, + "hidden", NSNull.Null, + "onLayout", NSNull.Null, + "onOrderIn", NSNull.Null, + "onOrderOut", NSNull.Null, + "position", NSNull.Null, + "sublayers", NSNull.Null, + "transform", NSNull.Null, + "bounds", NSNull.Null); + } + + public ObjectValueTreeViewController Controller { + get; private set; + } + + public bool CompactView { + get { return compactView; } + } + + internal NSFont CustomFont { + get; private set; + } + + public IObjectValueDebuggerService DebuggerService { + get; private set; + } + + /// + /// Gets a value indicating whether the user should be able to edit values in the tree + /// + public bool AllowEditing { + get => allowEditing; + set { + if (allowEditing != value) { + allowEditing = value; + ReloadData (); + } + } + } + + /// + /// Gets a value indicating whether or not the user should be able to expand nodes in the tree. + /// + public bool AllowExpanding { get; set; } + + /// + /// Gets a value indicating whether the user should be able to add watch expressions to the tree + /// + public bool AllowWatchExpressions { + get { return dataSource.AllowWatchExpressions; } + } + + /// + /// Gets or sets the pinned watch for the view. When a watch is pinned, the view should display only this value + /// + public PinnedWatch PinnedWatch { + get => pinnedWatch; + set { + if (pinnedWatch != value) { + pinnedWatch = value; + Runtime.RunInMainThread (() => { + if (value == null) { + pinColumn.MinWidth = pinColumn.MaxWidth = pinColumn.Width = MacDebuggerObjectPinView.MinWidth; + } else { + pinColumn.MinWidth = pinColumn.MaxWidth = pinColumn.Width = MacDebuggerObjectPinView.MaxWidth; + } + }).Ignore (); + } + } + } + + /// + /// Gets a value indicating the offset required for pinned watches + /// + public int PinnedWatchOffset { + get { + return (int) Frame.Height; + } + } + + double GetVisibleTableWidth () + { + var scrollView = EnclosingScrollView; + + if (scrollView != null) + return scrollView.DocumentVisibleRect.Width; + + return VisibleRect ().Width; + } + + internal void OnColumnResized () + { + if (autoresizing || compactView) + return; + + var width = GetVisibleTableWidth (); + + nameColumnWidth = nameColumn.Width / width; + valueColumnWidth = valueColumn.Width / width; + typeColumnWidth = typeColumn.Width / width; + } + + // Note: this resizing method is the one used by the Locals pad and the Watches pad + void ScaleColumnSizesToFit () + { + if (compactView || Superview == null || RowCount == 0) + return; + + var totalWidth = GetVisibleTableWidth (); + int columnWidth; + + try { + autoresizing = true; + + columnWidth = Math.Max ((int) (totalWidth * valueColumnWidth), MinimumNameColumnWidth); + valueColumn.Width = columnWidth; + + columnWidth = Math.Max ((int) (totalWidth * nameColumnWidth), MinimumValueColumnWidth); + nameColumn.Width = columnWidth; + + columnWidth = Math.Max ((int) (totalWidth * typeColumnWidth), MinimumTypeColumnWidth); + typeColumn.Width = columnWidth; + } finally { + autoresizing = false; + } + } + + // Note: this resizing method is the one used by debugger tooltips and pinned watches in the editor + void OptimizeColumnSizes (bool emitResized = true) + { + if (!compactView || Superview == null || RowCount == 0) + return; + + nfloat nameWidth = MinimumNameColumnWidth; + nfloat valueWidth = MinimumValueColumnWidth; + + for (nint row = 0; row < RowCount; row++) { + var rowView = GetRowView (row, true); + + if (rowView == null) + continue; + + var nameView = (MacDebuggerObjectNameView) rowView.ViewAtColumn (0); + + if (nameView != null) { + // Note: the Name column's X-offset is the width of the expander which we need to take that into account + // when calculating the Name column's width. + var width = nameView.Frame.X + nameView.OptimalWidth; + + if (width > nameWidth) + nameWidth = NMath.Min (width, nameColumn.MaxWidth); + } + + var valueView = (MacDebuggerObjectValueView) rowView.ViewAtColumn (1); + + if (valueView != null) { + var width = valueView.OptimalWidth; + + if (width > valueWidth) + valueWidth = NMath.Min (width, valueColumn.MaxWidth); + } + } + + try { + autoresizing = true; + + bool changed = false; + if (nameColumn.Width != nameWidth) { + nameColumn.MinWidth = nameColumn.Width = nameWidth; + changed = true; + } + if (valueColumn.Width != valueWidth) { + valueColumn.MinWidth = valueColumn.Width = valueWidth; + changed = true; + } + + if (changed) { + SizeToFit (); + + if (emitResized) + OnResized (); + } + + ReloadData (); + SetNeedsDisplayInRect (Frame); + } finally { + autoresizing = false; + } + } + + void UpdateColumnSizes () + { + if (compactView) + OptimizeColumnSizes (); + else + ScaleColumnSizesToFit (); + } + + static NSFont GetNSFontFromPangoFontDescription (Pango.FontDescription fontDescription) + { + if (fontDescription == null) + return null; + + return NSFontManager.SharedFontManager.FontWithFamilyWorkaround ( + fontDescription.Family, + fontDescription.Style == Pango.Style.Italic || fontDescription.Style == Pango.Style.Oblique + ? NSFontTraitMask.Italic + : 0, + NormalizeWeight (fontDescription.Weight), + fontDescription.Size / (nfloat) Pango.Scale.PangoScale); + + /// + /// Normalizes a Pango font weight (100-1000 scale) to a weight + /// suitable for NSFontDescription.FontWithFamily (0-15 scale). + /// + int NormalizeWeight (Pango.Weight pangoWeight) + { + double Normalize (double value, double inMin, double inMax, double outMin, double outMax) + => (outMax - outMin) / (inMax - inMin) * (value - inMax) + outMax; + + return (int) Math.Round (Normalize ((int) pangoWeight, 100, 1000, 0, 15)); + } + } + + internal void SetCustomFont (Pango.FontDescription fontDescription) + { + if (fontDescription != null) { + CustomFont = GetNSFontFromPangoFontDescription (fontDescription); + } else { + CustomFont = null; + } + + ReloadData (); + } + + internal void QueueResize () + { + } + + public override void ViewDidMoveToSuperview () + { + base.ViewDidMoveToSuperview (); + UpdateColumnSizes (); + } + + public override void ViewDidMoveToWindow () + { + base.ViewDidMoveToWindow (); + + if (compactView) + OptimizeColumnSizes (); + } + + public override void ViewDidEndLiveResize () + { + base.ViewDidEndLiveResize (); + UpdateColumnSizes (); + } + + public override void ViewDidUnhide () + { + base.ViewDidHide (); + UpdateColumnSizes (); + } + + public override void SetFrameSize (CGSize newSize) + { + base.SetFrameSize (newSize); + + //if (!autoresizing && !compactView) + // ScaleColumnSizesToFit (); + } + + /// + /// Triggered when the view tries to expand a node. This may trigger a load of + /// the node's children + /// + public event EventHandler NodeExpand; + + public void ExpandNode (ObjectValueNode node) + { + NodeExpand?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + /// + /// Triggered when the view tries to collapse a node. + /// + public event EventHandler NodeCollapse; + + public void CollapseNode (ObjectValueNode node) + { + NodeCollapse?.Invoke (this, new ObjectValueNodeEventArgs (node)); + OptimizeColumnSizes (false); + OnResized (); + } + + /// + /// Triggered when the view requests a node to fetch more of it's children + /// + public event EventHandler NodeLoadMoreChildren; + + internal void LoadMoreChildren (ObjectValueNode node) + { + NodeLoadMoreChildren?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + /// + /// Triggered when the view needs the node to be refreshed + /// + public event EventHandler NodeRefresh; + + internal void Refresh (ObjectValueNode node) + { + NodeRefresh?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + /// + /// Triggered when the view needs to know if the node can be edited + /// + public event EventHandler NodeGetCanEdit; + + internal bool GetCanEditNode (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeGetCanEdit?.Invoke (this, args); + return args.Response is bool b && b; + } + + /// + /// Triggered when the node's value has been edited by the user + /// + public event EventHandler NodeEditValue; + + internal bool GetEditValue (ObjectValueNode node, string newText) + { + var args = new ObjectValueEditEventArgs (node, newText); + NodeEditValue?.Invoke (this, args); + return args.Response is bool b && b; + } + + /// + /// Triggered when the user removes a node (an expression) + /// + public event EventHandler NodeRemoved; + + /// + /// Triggered when the user pins the node + /// + public event EventHandler NodePinned; + + void CreatePinnedWatch (ObjectValueNode node) + { + var expression = node.Expression; + + if (string.IsNullOrEmpty (expression)) + return; + + if (PinnedWatch != null) { + // Note: the row that the user just pinned will no longer be visible once + // all of the root children are collapsed. + currentHoverRow = -1; + + foreach (var child in dataSource.Root.Children) + CollapseItem (child, true); + } + + NodePinned?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + public void Pin (ObjectValueNode node) + { + CreatePinnedWatch (node); + } + + /// + /// Triggered when the pinned watch is removed by the user + /// + public event EventHandler NodeUnpinned; + + public void Unpin (ObjectValueNode node) + { + NodeUnpinned?.Invoke (this, EventArgs.Empty); + } + + /// + /// Triggered when the visualiser for the node should be shown + /// + public event EventHandler NodeShowVisualiser; + + internal bool ShowVisualizer (ObjectValueNode node) + { + var args = new ObjectValueNodeEventArgs (node); + NodeShowVisualiser?.Invoke (this, args); + return args.Response is bool b && b; + } + + /// + /// Triggered when an expression is added to the tree by the user + /// + public event EventHandler ExpressionAdded; + + internal void OnExpressionAdded (string expression) + { + ExpressionAdded?.Invoke (this, new ObjectValueExpressionEventArgs (null, expression)); + } + + /// + /// Triggered when an expression is edited by the user + /// + public event EventHandler ExpressionEdited; + + internal void OnExpressionEdited (ObjectValueNode node, string expression) + { + ExpressionEdited?.Invoke (this, new ObjectValueExpressionEventArgs (node, expression)); + } + + /// + /// Triggered when the user starts editing a node + /// + public event EventHandler StartEditing; + + internal void OnStartEditing () + { + StartEditing?.Invoke (this, EventArgs.Empty); + } + + /// + /// Triggered when the user stops editing a node + /// + public new event EventHandler EndEditing; + + internal void OnEndEditing () + { + EndEditing?.Invoke (this, EventArgs.Empty); + } + + void OnEvaluationCompleted (ObjectValueNode node, ObjectValueNode[] replacementNodes) + { + if (disposed) + return; + + dataSource.Replace (node, replacementNodes); + OptimizeColumnSizes (false); + OnResized (); + } + + public void LoadEvaluatedNode (ObjectValueNode node, ObjectValueNode[] replacementNodes) + { + OnEvaluationCompleted (node, replacementNodes); + } + + void OnChildrenLoaded (ObjectValueNode node, int startIndex, int count) + { + if (disposed) + return; + + dataSource.ReloadChildren (node); + OptimizeColumnSizes (false); + OnResized (); + } + + public void LoadNodeChildren (ObjectValueNode node, int startIndex, int count) + { + OnChildrenLoaded (node, startIndex, count); + } + + public void OnNodeExpanded (ObjectValueNode node) + { + if (disposed) + return; + + if (node.IsExpanded) { + // if the node is _still_ expanded then adjust UI and scroll + if (dataSource.TryGetValue (node, out var item)) { + if (!IsItemExpanded (item)) + ExpandItem (item); + } + + OptimizeColumnSizes (false); + OnResized (); + + // TODO: all this scrolling kind of seems awkward + //if (path != null) + // ScrollToCell (path, expCol, true, 0f, 0f); + } + } + + void IObjectValueTreeView.Cleared () + { + dataSource.Clear (); + } + + void IObjectValueTreeView.Appended (ObjectValueNode node) + { + dataSource.Append (node); + } + + void IObjectValueTreeView.Appended (IList nodes) + { + dataSource.Append (nodes); + } + + static CGPoint ConvertPointFromEvent (NSView view, NSEvent theEvent) + { + var point = theEvent.LocationInWindow; + + if (view.Window != null && theEvent.WindowNumber != view.Window.WindowNumber) { + var rect = theEvent.Window.ConvertRectToScreen (new CGRect (point, new CGSize (1, 1))); + rect = view.Window.ConvertRectFromScreen (rect); + point = rect.Location; + } + + return view.ConvertPointFromView (point, null); + } + + void UpdatePreviewIcon (nint row, PreviewButtonIcon icon) + { + if (row >= RowCount) + return; + + var rowView = GetRowView (row, false); + + if (rowView != null) { + var nameView = (MacDebuggerObjectNameView) rowView.ViewAtColumn (0); + + nameView.SetPreviewButtonIcon (icon); + } + } + + void UpdatePinIcon (nint row, bool hover) + { + if (row >= RowCount) + return; + + if (pinColumn == null) + return; + + var rowView = GetRowView (row, false); + + if (rowView != null) { + var pinView = (MacDebuggerObjectPinView) rowView.ViewAtColumn (ColumnCount - 1); + + pinView.SetMouseHover (hover); + } + } + + void UpdateCellViewIcons (NSEvent theEvent) + { + var point = ConvertPointFromEvent (this, theEvent); + var row = GetRow (point); + + if (row != currentHoverRow) { + if (currentHoverRow != -1) { + UpdatePreviewIcon (currentHoverRow, PreviewButtonIcon.Hidden); + currentHoverIcon = PreviewButtonIcon.Hidden; + UpdatePinIcon (currentHoverRow, false); + } + currentHoverRow = row; + } + + if (row == -1) + return; + + PreviewButtonIcon icon; + + if (GetColumn (point) == 0) { + icon = PreviewButtonIcon.Hover; + } else { + icon = PreviewButtonIcon.RowHover; + } + + currentHoverIcon = icon; + + if (IsRowSelected (row)) + icon = PreviewButtonIcon.Selected; + + UpdatePreviewIcon (row, icon); + UpdatePinIcon (row, true); + } + + void OnPreviewWindowClosed (object sender, EventArgs args) + { + if (currentHoverRow != -1) { + UpdatePreviewIcon (currentHoverRow, PreviewButtonIcon.Hidden); + currentHoverIcon = PreviewButtonIcon.Hidden; + } + } + + public override void MouseEntered (NSEvent theEvent) + { + UpdateCellViewIcons (theEvent); + base.MouseEntered (theEvent); + } + + public override void MouseExited (NSEvent theEvent) + { + if (currentHoverRow != -1) { + UpdatePreviewIcon (currentHoverRow, PreviewButtonIcon.Hidden); + currentHoverIcon = PreviewButtonIcon.Hidden; + currentHoverRow = -1; + + UpdatePinIcon (currentHoverRow, false); + } + + base.MouseExited (theEvent); + } + + public override void MouseMoved (NSEvent theEvent) + { + UpdateCellViewIcons (theEvent); + base.MouseMoved (theEvent); + } + + internal static bool ValidObjectForPreviewIcon (ObjectValueNode node) + { + var obj = node.GetDebuggerObjectValue (); + if (obj == null) + return false; + + if (obj.IsNull) + return false; + + if (obj.IsPrimitive) { + //obj.DisplayValue.Contains ("|") is special case to detect enum with [Flags] + return obj.TypeName == "string" || (obj.DisplayValue != null && obj.DisplayValue.Contains ("|")); + } + + if (string.IsNullOrEmpty (obj.TypeName)) + return false; + + return true; + } + + void OnSelectionChanged (object sender, EventArgs e) + { + if (currentHoverRow == -1) + return; + + var row = SelectedRow; + + if (SelectedRowCount == 0 || row != currentHoverRow) { + // reset back to what the unselected icon would be + UpdatePreviewIcon (currentHoverRow, currentHoverIcon); + return; + } + + UpdatePreviewIcon (currentHoverRow, PreviewButtonIcon.Selected); + } + + public event EventHandler Resized; + + void OnResized () + { + Resized?.Invoke (this, EventArgs.Empty); + } + + [CommandUpdateHandler (EditCommands.SelectAll)] + protected void UpdateSelectAll (CommandInfo cmd) + { + cmd.Enabled = Controller.Root.Children.Count > 0; + } + + [CommandHandler (EditCommands.SelectAll)] + protected void OnSelectAll () + { + SelectAll (this); + } + + [CommandHandler (EditCommands.Copy)] + protected void OnCopy () + { + if (SelectedRowCount == 0) + return; + + var str = new StringBuilder (); + var needsNewLine = false; + + var selectedRows = SelectedRows; + foreach (var row in selectedRows) { + var item = (MacObjectValueNode) ItemAtRow ((nint) row); + + if (item.Target is AddNewExpressionObjectValueNode || + item.Target is ShowMoreValuesObjectValueNode || + item.Target is LoadingObjectValueNode) + break; + + if (needsNewLine) + str.AppendLine (); + + needsNewLine = true; + + var value = item.Target.DisplayValue; + var type = item.Target.TypeName; + + if (type == "string") { + var objVal = item.Target.GetDebuggerObjectValue (); + + if (objVal != null) { + // HACK: we need a better abstraction of the stack frame, better yet would be to not really need it in the view + var opt = DebuggerService.Frame.GetStackFrame ().DebuggerSession.Options.EvaluationOptions.Clone (); + opt.EllipsizeStrings = false; + value = '"' + Mono.Debugging.Evaluation.ExpressionEvaluator.EscapeString ((string)objVal.GetRawValue (opt)) + '"'; + } + } + + str.Append (value); + } + + var clipboard = NSPasteboard.GeneralPasteboard; + + clipboard.ClearContents (); + clipboard.SetStringForType (str.ToString (), NSPasteboard.NSPasteboardTypeString); + + //Gtk.Clipboard.Get (Gdk.Selection.Clipboard).Text = str.ToString (); + } + + void OnCopy (object sender, EventArgs args) + { + OnCopy (); + } + + [CommandHandler (EditCommands.Delete)] + [CommandHandler (EditCommands.DeleteKey)] + protected void OnDelete () + { + var nodesToDelete = new List (); + var selectedRows = SelectedRows; + + foreach (var row in selectedRows) { + var item = (MacObjectValueNode) ItemAtRow ((nint) row); + + nodesToDelete.Add (item.Target); + } + + foreach (var node in nodesToDelete) + NodeRemoved?.Invoke (this, new ObjectValueNodeEventArgs (node)); + } + + void OnDelete (object sender, EventArgs args) + { + OnDelete (); + } + + bool CanDelete (out bool enabled) + { + enabled = false; + + if (!AllowWatchExpressions) + return false; + + if (SelectedRowCount == 0) + return false; + + enabled = true; + + var selectedRows = SelectedRows; + foreach (var row in selectedRows) { + var item = (MacObjectValueNode)ItemAtRow ((nint)row); + + if (!(item.Target.Parent is RootObjectValueNode)) { + enabled = false; + break; + } + } + + return true; + } + + [CommandUpdateHandler (EditCommands.Delete)] + [CommandUpdateHandler (EditCommands.DeleteKey)] + protected void OnUpdateDelete (CommandInfo cinfo) + { + cinfo.Visible = CanDelete (out bool enabled); + cinfo.Enabled = enabled; + } + + [CommandHandler (DebugCommands.AddWatch)] + protected void OnAddWatch () + { + var expressions = new List (); + var selectedRows = SelectedRows; + + foreach (var row in selectedRows) { + var item = (MacObjectValueNode) ItemAtRow ((nint) row); + var expression = item.Target.Expression; + + if (!string.IsNullOrEmpty (expression)) + expressions.Add (expression); + } + + foreach (var expression in expressions) + DebuggingService.AddWatch (expression); + } + + void OnAddWatch (object sender, EventArgs args) + { + OnAddWatch (); + } + + bool CanAddWatch (out bool enabled) + { + enabled = SelectedRowCount > 0; + + return true; + } + + [CommandUpdateHandler (DebugCommands.AddWatch)] + protected void OnUpdateAddWatch (CommandInfo cinfo) + { + cinfo.Visible = CanAddWatch (out bool enabled); + cinfo.Enabled = enabled; + } + + [CommandHandler (EditCommands.Rename)] + protected void OnRename () + { + var nameView = (MacDebuggerObjectNameView) GetView (0, SelectedRow, false); + + nameView.TextField.BecomeFirstResponder (); + } + + void OnRename (object sender, EventArgs args) + { + OnRename (); + } + + bool CanRename (out bool enabled) + { + enabled = SelectedRowCount == 1; + + return AllowWatchExpressions; + } + + [CommandUpdateHandler (EditCommands.Rename)] + protected void OnUpdateRename (CommandInfo cinfo) + { + cinfo.Visible = CanRename (out bool enabled); + cinfo.Enabled = enabled; + } + + public override NSMenu MenuForEvent (NSEvent theEvent) + { + if (!allowPopupMenu) + return null; + + var point = ConvertPointFromEvent (this, theEvent); + var row = GetRow (point); + + if (row < 0) + return null; + + var menu = new NSMenu (); + bool enabled; + + if (CanAddWatch (out enabled)) { + menu.AddItem (new NSMenuItem (GettextCatalog.GetString ("Add Watch"), OnAddWatch) { + Enabled = enabled + }); + menu.AddItem (NSMenuItem.SeparatorItem); + } + + menu.AddItem (new NSMenuItem (GettextCatalog.GetString ("Copy"), OnCopy)); + + if (CanRename (out enabled)) { + menu.AddItem (new NSMenuItem (GettextCatalog.GetString ("Rename"), OnRename) { + Enabled = enabled + }); + } + + if (CanDelete (out enabled)) { + menu.AddItem (new NSMenuItem (GettextCatalog.GetString ("Delete"), OnDelete) { + Enabled = enabled + }); + } + + return menu; + } + + protected override void Dispose (bool disposing) + { + if (disposing && !disposed) { + PreviewWindowManager.WindowClosed -= OnPreviewWindowClosed; + PreviewWindowManager.DestroyWindow (); + treeViewDelegate.SelectionChanged -= OnSelectionChanged; + treeViewDelegate.Dispose (); + treeViewDelegate = null; + dataSource.Dispose (); + dataSource = null; + disposed = true; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDataSource.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDataSource.cs new file mode 100644 index 0000000000..52cc05da9f --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDataSource.cs @@ -0,0 +1,343 @@ +// +// MacObjectValueTreeViewDataSource.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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.Collections.Generic; + +using AppKit; +using Foundation; + +namespace MonoDevelop.Debugger +{ + /// + /// The data source for the Cocoa implementation of the ObjectValueTreeView. + /// + class MacObjectValueTreeViewDataSource : NSOutlineViewDataSource + { + readonly Dictionary mapping = new Dictionary (); + readonly MacObjectValueTreeView treeView; + + public MacObjectValueTreeViewDataSource (MacObjectValueTreeView treeView, ObjectValueNode root, bool allowWatchExpressions) + { + AllowWatchExpressions = allowWatchExpressions; + this.treeView = treeView; + + Root = new MacObjectValueNode (null, root); + mapping.Add (root, Root); + + foreach (var child in root.Children) + Add (Root, child); + + if (allowWatchExpressions) + Add (Root, new AddNewExpressionObjectValueNode ()); + } + + public MacObjectValueNode Root { + get; private set; + } + + public bool AllowWatchExpressions { + get; private set; + } + + public bool TryGetValue (ObjectValueNode node, out MacObjectValueNode value) + { + return mapping.TryGetValue (node, out value); + } + + void Add (MacObjectValueNode parent, ObjectValueNode node) + { + var value = new MacObjectValueNode (parent, node); + mapping[node] = value; + + parent.Children.Add (value); + + foreach (var child in node.Children) + Add (value, child); + + if (node.HasChildren && !node.ChildrenLoaded) + Add (value, new LoadingObjectValueNode (node)); + } + + void Insert (MacObjectValueNode parent, int index, ObjectValueNode node) + { + var value = new MacObjectValueNode (parent, node); + mapping[node] = value; + + parent.Children.Insert (index, value); + + foreach (var child in node.Children) + Add (value, child); + + if (node.HasChildren && !node.ChildrenLoaded) + Add (value, new LoadingObjectValueNode (node)); + } + + void Remove (MacObjectValueNode node) + { + foreach (var child in node.Children) + Remove (child); + + mapping.Remove (node.Target); + node.Children.Clear (); + node.Dispose (); + } + + public void Replace (ObjectValueNode node, ObjectValueNode[] replacementNodes) + { + if (!TryGetValue (node, out var item)) + return; + + var parent = item.Parent; + int index = -1; + + if (parent == null) + return; + + for (int i = 0; i < parent.Children.Count; i++) { + if (parent.Children[i] == item) { + index = i; + break; + } + } + + if (index == -1) + return; + + parent.Children.RemoveAt (index); + mapping.Remove (item.Target); + item.Dispose (); + + treeView.BeginUpdates (); + + var indexes = new NSIndexSet (index); + + if (parent.Target is RootObjectValueNode) + treeView.RemoveItems (indexes, null, NSTableViewAnimation.None); + else + treeView.RemoveItems (indexes, parent, NSTableViewAnimation.None); + + if (replacementNodes.Length > 0) { + for (int i = 0; i < replacementNodes.Length; i++) + Insert (parent, index + i, replacementNodes [i]); + + var range = new NSRange (index, replacementNodes.Length); + indexes = NSIndexSet.FromNSRange (range); + + if (parent.Target is RootObjectValueNode) + treeView.InsertItems (indexes, null, NSTableViewAnimation.None); + else + treeView.InsertItems (indexes, parent, NSTableViewAnimation.None); + + foreach (var n in replacementNodes) { + if (treeView.Controller.GetNodeWasExpandedAtLastCheckpoint (n)) { + if (TryGetValue (n, out MacObjectValueNode x)) { + treeView.ExpandItem (x); + } + } + } + } + + treeView.EndUpdates (); + } + + public void ReloadChildren (ObjectValueNode node) + { + if (!TryGetValue (node, out var parent)) + return; + + treeView.BeginUpdates (); + + NSIndexSet indexes; + NSRange range; + + if (parent.Children.Count > 0) { + range = new NSRange (0, parent.Children.Count); + indexes = NSIndexSet.FromNSRange (range); + + foreach (var child in parent.Children) { + mapping.Remove (child.Target); + child.Dispose (); + } + + parent.Children.Clear (); + + if (parent.Target is RootObjectValueNode) + treeView.RemoveItems (indexes, null, NSTableViewAnimation.None); + else + treeView.RemoveItems (indexes, parent, NSTableViewAnimation.None); + } + + for (int i = 0; i < node.Children.Count; i++) + Add (parent, node.Children[i]); + + // if we did not load all the children, add a Show More node + if (!node.ChildrenLoaded) + Add (parent, new ShowMoreValuesObjectValueNode (node)); + + range = new NSRange (0, parent.Children.Count); + indexes = NSIndexSet.FromNSRange (range); + + if (parent.Target is RootObjectValueNode) + treeView.InsertItems (indexes, null, NSTableViewAnimation.None); + else + treeView.InsertItems (indexes, parent, NSTableViewAnimation.None); + + // if we loaded children and discovered that the node does not actually have any children, + // update the node and reload the data. + // TOOD: it would be nice to know this before the node is expanded so we don't see the "loading" node flash + if (!node.HasChildren) { + treeView.ReloadItem (parent); + } else { + // expand any items that we loaded that were expanded previously + + foreach (var n in node.Children) { + if (treeView.Controller.GetNodeWasExpandedAtLastCheckpoint (n)) { + if (TryGetValue (n, out MacObjectValueNode x)) { + treeView.ExpandItem (x); + } + } + } + } + + treeView.EndUpdates (); + } + + public void Clear () + { + int count = Root.Children.Count; + + if (AllowWatchExpressions) + count--; + + for (int i = count - 1; i >= 0; i--) { + var child = Root.Children[i]; + Root.Children.RemoveAt (i); + Remove (child); + } + + if (count <= 0) + return; + + var range = new NSRange (0, count); + var indexes = NSIndexSet.FromNSRange (range); + + treeView.RemoveItems (indexes, null, NSTableViewAnimation.None); + } + + public void Append (ObjectValueNode node) + { + int index; + + if (AllowWatchExpressions) { + index = Root.Children.Count - 1; + Core.LoggingService.LogInfo ("MacObjectValueTreeViewDataSource.Append: Inserting '{0}' at index {1}", node.Name, index); + Insert (Root, index, node); + } else { + index = Root.Children.Count; + Core.LoggingService.LogInfo ("MacObjectValueTreeViewDataSource.Append: Adding '{0}' at index {1}", node.Name, index); + Add (Root, node); + } + + var indexes = NSIndexSet.FromIndex (index); + + treeView.InsertItems (indexes, null, NSTableViewAnimation.None); + + if (treeView.Controller.GetNodeWasExpandedAtLastCheckpoint (node)) { + if (TryGetValue(node, out MacObjectValueNode x)) { + treeView.ExpandItem (x); + } + } + } + + public void Append (IList nodes) + { + int index; + + if (AllowWatchExpressions) { + index = Root.Children.Count - 1; + for (int i = 0; i < nodes.Count; i++) + Insert (Root, index + i, nodes[i]); + } else { + index = Root.Children.Count; + for (int i = 0; i < nodes.Count; i++) + Add (Root, nodes[i]); + } + + var range = new NSRange (index, nodes.Count); + var indexes = NSIndexSet.FromNSRange (range); + + treeView.InsertItems (indexes, null, NSTableViewAnimation.None); + + foreach (var node in nodes) { + if (treeView.Controller.GetNodeWasExpandedAtLastCheckpoint (node)) { + if (TryGetValue (node, out MacObjectValueNode x)) { + treeView.ExpandItem (x); + } + } + } + } + + public override nint GetChildrenCount (NSOutlineView outlineView, NSObject item) + { + var node = (item as MacObjectValueNode) ?? Root; + + if (node == null) + return 0; + + return node.Children.Count; + } + + public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, NSObject item) + { + var node = (item as MacObjectValueNode) ?? Root; + + if (node == null || childIndex >= node.Children.Count) + return null; + + return node.Children[(int) childIndex]; + } + + public override bool ItemExpandable (NSOutlineView outlineView, NSObject item) + { + var node = (item as MacObjectValueNode) ?? Root; + + return node != null && node.Children.Count > 0; + } + + protected override void Dispose (bool disposing) + { + if (disposing) { + foreach (var kvp in mapping) + kvp.Value.Dispose (); + mapping.Clear (); + Root = null; + } + + base.Dispose (disposing); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDelegate.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDelegate.cs new file mode 100644 index 0000000000..8fa739adb9 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/Mac/MacObjectValueTreeViewDelegate.cs @@ -0,0 +1,155 @@ +// +// MacObjectValueTreeViewDelegate.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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 AppKit; +using Foundation; + +namespace MonoDevelop.Debugger +{ + /// + /// The worker delegate for the Cocoa implementation of the ObjectValueTreeView. + /// + class MacObjectValueTreeViewDelegate : NSOutlineViewDelegate + { + static readonly NSString NSObjectKey = new NSString ("NSObject"); + readonly MacObjectValueTreeView treeView; + + public MacObjectValueTreeViewDelegate (MacObjectValueTreeView treeView) + { + this.treeView = treeView; + } + + public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) + { + var view = (MacDebuggerObjectCellViewBase) outlineView.MakeView (tableColumn.Identifier, this); + + switch (tableColumn.Identifier) { + case "name": + if (view == null) + view = new MacDebuggerObjectNameView (treeView); + break; + case "value": + if (view == null) + view = new MacDebuggerObjectValueView (treeView); + break; + case "type": + if (view == null) + view = new MacDebuggerObjectTypeView (treeView); + break; + case "pin": + if (view == null) + view = new MacDebuggerObjectPinView (treeView); + break; + default: + return null; + } + + view.Row = outlineView.RowForItem (item); + view.ObjectValue = item; + + return view; + } + + public override void ColumnDidResize (NSNotification notification) + { + treeView.OnColumnResized (); + } + +#if false + public override nfloat GetSizeToFitColumnWidth (NSOutlineView outlineView, nint column) + { + var columns = outlineView.TableColumns (); + var col = columns[column]; + var columnWidth = col.MinWidth; + + for (nint row = 0; row < outlineView.RowCount; row++) { + var rowView = outlineView.GetRowView (row, true); + var columnView = rowView.ViewAtColumn (column); + var width = columnView.Frame.Width; + + if (column == 0) + width += columnView.Frame.X; + + if (width > columnWidth) + columnWidth = NMath.Min (width, col.MaxWidth); + } + + return columnWidth; + } +#endif + + public override void ItemDidCollapse (NSNotification notification) + { + //var outlineView = (NSOutlineView) notification.Object; + + if (!notification.UserInfo.TryGetValue (NSObjectKey, out var value)) + return; + + var node = (value as MacObjectValueNode)?.Target; + + if (node == null) + return; + + treeView.CollapseNode (node); + } + + public override void ItemDidExpand (NSNotification notification) + { + //var outlineView = (NSOutlineView) notification.Object; + + if (!notification.UserInfo.TryGetValue (NSObjectKey, out var value)) + return; + + var node = value as MacObjectValueNode; + + if (node == null) + return; + + node.HideValueButton = true; + treeView.ReloadItem (node, false); + treeView.ExpandNode (node.Target); + } + + public override bool ShouldExpandItem (NSOutlineView outlineView, NSObject item) + { + if (!treeView.AllowExpanding) + return false; + + var node = (item as MacObjectValueNode)?.Target; + + return node != null && node.HasChildren; + } + + public event EventHandler SelectionChanged; + + public override void SelectionDidChange (NSNotification notification) + { + SelectionChanged?.Invoke (this, EventArgs.Empty); + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeView.cs index e275996801..03e57b43cf 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeView.cs @@ -40,7 +40,6 @@ using MonoDevelop.Ide.CodeCompletion; using MonoDevelop.Components.Commands; using MonoDevelop.Ide.Commands; using MonoDevelop.Ide.Editor.Extension; -using System.Linq; using MonoDevelop.Ide.Fonts; namespace MonoDevelop.Debugger @@ -759,9 +758,8 @@ namespace MonoDevelop.Debugger } } - public string PinnedWatchFile { get; set; } - public int PinnedWatchLine { get; set; } - + public PinnedWatchLocation PinnedWatchLocation { get; set; } + public bool CompactView { get { return compact; @@ -2285,13 +2283,11 @@ namespace MonoDevelop.Debugger if (PinnedWatch != null) { CollapseAll (); - watch.File = PinnedWatch.File; - watch.Line = PinnedWatch.Line; + watch.Location = PinnedWatch.Location; watch.OffsetX = PinnedWatch.OffsetX; watch.OffsetY = PinnedWatch.OffsetY + SizeRequest ().Height + 5; } else { - watch.File = PinnedWatchFile; - watch.Line = PinnedWatchLine; + watch.Location = PinnedWatchLocation; watch.OffsetX = -1; // means that the watch should be placed at the line coordinates defined by watch.Line watch.OffsetY = -1; } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewController.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewController.cs index 42773a18c2..acf1c691c2 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewController.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewController.cs @@ -37,6 +37,16 @@ using MonoDevelop.Components; namespace MonoDevelop.Debugger { + public enum PreviewButtonIcon + { + None, + Hidden, + RowHover, + Hover, + Active, + Selected, + } + public interface IObjectValueDebuggerService { bool CanQueryDebugger { get; } @@ -71,8 +81,10 @@ namespace MonoDevelop.Debugger /// readonly Dictionary oldValues = new Dictionary (); - public ObjectValueTreeViewController () + public ObjectValueTreeViewController (bool allowWatchExpressions = false) { + AllowWatchExpressions = allowWatchExpressions; + Root = new RootObjectValueNode (); } public IDebuggerService Debugger { @@ -119,13 +131,7 @@ namespace MonoDevelop.Debugger /// Gets a value indicating whether the user should be able to add watch expressions to the tree /// public bool AllowWatchExpressions { - get => allowWatchExpressions; - set { - allowWatchExpressions = value; - if (view != null) { - view.AllowWatchExpressions = value; - } - } + get; private set; } public PinnedWatch PinnedWatch { @@ -137,11 +143,7 @@ namespace MonoDevelop.Debugger } } - public string PinnedWatchFile { - get; set; - } - - public int PinnedWatchLine { + public PinnedWatchLocation PinnedWatchLocation { get; set; } @@ -151,16 +153,12 @@ namespace MonoDevelop.Debugger } } - public object GetControl (bool headersVisible = true, bool compactView = false, bool allowPinning = false, bool allowPopupMenu = true, bool rootPinVisible = false) + protected void ConfigureView (IObjectValueTreeView control) { - if (view != null) - throw new InvalidOperationException ("You can only get the control once for each controller instance"); - - view = new GtkObjectValueTreeView (this, this, AllowEditing, headersVisible, AllowWatchExpressions, compactView, allowPinning, allowPopupMenu, rootPinVisible) { - AllowExpanding = this.AllowExpanding, - PinnedWatch = this.PinnedWatch, - }; + view = control; + view.AllowExpanding = AllowExpanding; + view.PinnedWatch = PinnedWatch; view.NodeExpand += OnViewNodeExpand; view.NodeCollapse += OnViewNodeCollapse; @@ -174,8 +172,38 @@ namespace MonoDevelop.Debugger view.NodePinned += OnViewNodePinned; view.NodeUnpinned += OnViewNodeUnpinned; view.NodeShowVisualiser += OnViewNodeShowVisualiser; + } + + public GtkObjectValueTreeView GetGtkControl (bool headersVisible = true, bool compactView = false, bool allowPinning = false, bool allowPopupMenu = true, bool rootPinVisible = false) + { + if (view != null) + throw new InvalidOperationException ("You can only get the control once for each controller instance"); + + var control = new GtkObjectValueTreeView (this, this, AllowEditing, headersVisible, compactView, allowPinning, allowPopupMenu, rootPinVisible); + + ConfigureView (control); - return view; + return control; + } + + public MacObjectValueTreeView GetMacControl (bool headersVisible = true, bool compactView = false, bool allowPinning = false, bool allowPopupMenu = true, bool rootPinVisible = false) + { + if (view != null) + throw new InvalidOperationException ("You can only get the control once for each controller instance"); + + var control = new MacObjectValueTreeView (this, this, AllowEditing, headersVisible, compactView, allowPinning, allowPopupMenu, rootPinVisible); + + ConfigureView (control); + + return control; + } + + public Control GetControl (bool headersVisible = true, bool compactView = false, bool allowPinning = false, bool allowPopupMenu = true, bool rootPinVisible = false) + { + if (Platform.IsMac) + return GetMacControl (headersVisible, compactView, allowPinning, allowPopupMenu, rootPinVisible); + + return GetGtkControl (headersVisible, compactView, allowPinning, allowPopupMenu, rootPinVisible); } public void CancelAsyncTasks () @@ -188,10 +216,10 @@ namespace MonoDevelop.Debugger /// public void ClearValues () { - Root = OnCreateRoot (); + ((RootObjectValueNode) Root).Clear (); Runtime.RunInMainThread (() => { - view.Reload (Root); + view.Cleared (); }).Ignore (); } @@ -209,15 +237,12 @@ namespace MonoDevelop.Debugger /// public void AddValue (ObjectValueNode value) { - if (Root == null) { - Root = OnCreateRoot (); - } - ((RootObjectValueNode) Root).AddValue (value); RegisterNode (value); Runtime.RunInMainThread (() => { - view.Reload (Root); + LoggingService.LogInfo ("Appending value '{0}' to tree view", value.Name); + view.Appended (value); }).Ignore (); } @@ -226,23 +251,19 @@ namespace MonoDevelop.Debugger /// public void AddValues (IEnumerable values) { - if (Root == null) { - Root = OnCreateRoot (); - } - var nodes = values.ToList (); + ((RootObjectValueNode) Root).AddValues (nodes); - foreach (var node in nodes) { + foreach (var node in nodes) RegisterNode (node); - } Runtime.RunInMainThread (() => { - view.Reload (Root); + view.Appended (nodes); }).Ignore (); } - public async Task GetCompletionDataAsync (string expression, CancellationToken token) + public async Task GetCompletionDataAsync (string expression, CancellationToken token) { if (CanQueryDebugger && Frame != null) { // TODO: improve how we get at the underlying real stack frame @@ -257,13 +278,11 @@ namespace MonoDevelop.Debugger var watch = new PinnedWatch (); if (PinnedWatch != null) { - watch.File = PinnedWatch.File; - watch.Line = PinnedWatch.Line; + watch.Location = PinnedWatch.Location; watch.OffsetX = PinnedWatch.OffsetX; watch.OffsetY = PinnedWatch.OffsetY + height + 5; } else { - watch.File = PinnedWatchFile; - watch.Line = PinnedWatchLine; + watch.Location = PinnedWatchLocation; watch.OffsetX = -1; // means that the watch should be placed at the line coordinates defined by watch.Line watch.OffsetY = -1; } @@ -354,9 +373,12 @@ namespace MonoDevelop.Debugger public void AddExpression (string expression) { - if (!AllowWatchExpressions) + if (!AllowWatchExpressions) { + LoggingService.LogInfo ("Watch expressions not allowed."); return; + } + LoggingService.LogInfo ("Evaluating expression '{0}'", expression); var node = Frame.EvaluateExpression (expression); AddValue (node); } @@ -533,6 +555,7 @@ namespace MonoDevelop.Debugger void OnViewExpressionAdded (object sender, ObjectValueExpressionEventArgs e) { + LoggingService.LogInfo ("ObjectValueTreeViewController.OnViewExpressionAdded"); AddExpression (e.Expression); } @@ -602,9 +625,8 @@ namespace MonoDevelop.Debugger } await Runtime.RunInMainThread (() => { - if (loadedCount > 0) { - view.LoadNodeChildren (node, 0, node.Children.Count); - } + // tell the view about the children, even if there are, in fact, none + view.LoadNodeChildren (node, 0, node.Children.Count); view.OnNodeExpanded (node); }); @@ -620,25 +642,25 @@ namespace MonoDevelop.Debugger if (childFetchTasks.TryGetValue (node, out Task task)) { // there is already a task to fetch the children return await task; - } else { - try { - var oldCount = node.Children.Count; - var result = await node.LoadChildrenAsync (MaxEnumerableChildrenToFetch, cancellationTokenSource.Token); + } - // if any of them are still evaluating register for - // a completion event so that we can tell the UI - for (int i = oldCount; i < oldCount + result; i++) { - var c = node.Children[i]; - RegisterNode (c); - } + try { + var oldCount = node.Children.Count; + var result = await node.LoadChildrenAsync (MaxEnumerableChildrenToFetch, cancellationTokenSource.Token); + + // if any of them are still evaluating register for + // a completion event so that we can tell the UI + for (int i = oldCount; i < oldCount + result; i++) { + var c = node.Children[i]; + RegisterNode (c); + } - // always send the event so that the UI can determine if the node has finished loading. - OnChildrenLoaded (node, oldCount, result); + // always send the event so that the UI can determine if the node has finished loading. + OnChildrenLoaded (node, oldCount, result); - return result; - } finally { - childFetchTasks.Remove (node); - } + return result; + } finally { + childFetchTasks.Remove (node); } } catch (Exception ex) { LoggingService.LogInternalError (ex); @@ -661,33 +683,33 @@ namespace MonoDevelop.Debugger if (childFetchTasks.TryGetValue (node, out Task task)) { // there is already a task to fetch the children return await task; - } else { - try { - int result = 0; - if (count > 0) { - var oldCount = node.Children.Count; - result = await node.LoadChildrenAsync (count, cancellationToken); - - // if any of them are still evaluating register for - // a completion event so that we can tell the UI - for (int i = oldCount; i < oldCount + result; i++) { - var c = node.Children[i]; - RegisterNode (c); - } - } else { - result = await node.LoadChildrenAsync (cancellationToken); - - // if any of them are still evaluating register for - // a completion event so that we can tell the UI - foreach (var c in node.Children) { - RegisterNode (c); - } + } + + try { + int result = 0; + if (count > 0) { + var oldCount = node.Children.Count; + result = await node.LoadChildrenAsync (count, cancellationToken); + + // if any of them are still evaluating register for + // a completion event so that we can tell the UI + for (int i = oldCount; i < oldCount + result; i++) { + var c = node.Children[i]; + RegisterNode (c); } + } else { + result = await node.LoadChildrenAsync (cancellationToken); - return result; - } finally { - childFetchTasks.Remove (node); + // if any of them are still evaluating register for + // a completion event so that we can tell the UI + foreach (var c in node.Children) { + RegisterNode (c); + } } + + return result; + } finally { + childFetchTasks.Remove (node); } } catch (Exception ex) { LoggingService.LogInternalError (ex); @@ -737,15 +759,6 @@ namespace MonoDevelop.Debugger #endregion - - /// - /// Called when clearing, by default sets the root to a new ObjectValueNode - /// - protected virtual ObjectValueNode OnCreateRoot () - { - return new RootObjectValueNode (); - } - protected virtual IDebuggerService OnGetDebuggerService () { return new ObjectValueDebuggerService (); @@ -881,6 +894,18 @@ namespace MonoDevelop.Debugger return "md-" + access + global + source; } + + public static string GetPreviewButtonIcon (PreviewButtonIcon icon) + { + switch (icon) { + case PreviewButtonIcon.Hidden: return "md-empty"; + case PreviewButtonIcon.RowHover: return "md-preview-normal"; + case PreviewButtonIcon.Hover: return "md-preview-hover"; + case PreviewButtonIcon.Active: return "md-preview-active"; + case PreviewButtonIcon.Selected: return "md-preview-selected"; + default: return null; + } + } } #region Extension methods and helpers diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewFakes.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewFakes.cs index a01eda916a..d96e5b169c 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewFakes.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/ObjectValueTreeViewFakes.cs @@ -34,7 +34,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - abstract class DebugObjectValueNode : ObjectValueNode + public abstract class DebugObjectValueNode : ObjectValueNode { protected DebugObjectValueNode (string name) : base (name) { @@ -50,7 +50,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - sealed class FakeIndexedObjectValueNode : DebugObjectValueNode + public sealed class FakeIndexedObjectValueNode : DebugObjectValueNode { public FakeIndexedObjectValueNode (int index) : base ($"indexed[{index}]") { @@ -67,7 +67,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - sealed class FakeIsImplicitNotSupportedObjectValueNode : DebugObjectValueNode + public sealed class FakeIsImplicitNotSupportedObjectValueNode : DebugObjectValueNode { string value; bool isImplicitNotSupported; @@ -95,7 +95,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - sealed class FakeObjectValueNode : DebugObjectValueNode + public sealed class FakeObjectValueNode : DebugObjectValueNode { bool hasChildren; @@ -120,7 +120,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - sealed class FakeEnumerableObjectValueNode : DebugObjectValueNode + public sealed class FakeEnumerableObjectValueNode : DebugObjectValueNode { readonly int maxItems; @@ -164,7 +164,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - sealed class FakeEvaluatingObjectValueNode : DebugObjectValueNode + public sealed class FakeEvaluatingObjectValueNode : DebugObjectValueNode { bool isEvaluating; bool hasChildren; @@ -201,7 +201,7 @@ namespace MonoDevelop.Debugger /// /// An AbstractObjectValueNode used for debugging /// - sealed class FakeEvaluatingGroupObjectValueNode : DebugObjectValueNode, IEvaluatingGroupObjectValueNode + public sealed class FakeEvaluatingGroupObjectValueNode : DebugObjectValueNode, IEvaluatingGroupObjectValueNode { bool isEvaluating; int evalNodes; diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/RootObjectValueNode.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/RootObjectValueNode.cs index c20c63fef5..5669837d20 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/RootObjectValueNode.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValue/RootObjectValueNode.cs @@ -62,6 +62,11 @@ namespace MonoDevelop.Debugger ReplaceChildAt (index, value); } + public void Clear () + { + ClearChildren (); + } + void ISupportChildObjectValueNodeReplacement.ReplaceChildNode (ObjectValueNode node, ObjectValueNode[] newNodes) { var index = Children.IndexOf (node); diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValuePad.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValuePad.cs index 1d9732bc66..3558d1877a 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValuePad.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValuePad.cs @@ -34,54 +34,100 @@ using Mono.Debugging.Client; using MonoDevelop.Core; using MonoDevelop.Ide.Gui; using MonoDevelop.Components; +using Foundation; namespace MonoDevelop.Debugger { public class ObjectValuePad : PadContent { - protected readonly bool UseNewTreeView = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", false); + protected readonly bool UseNewTreeView = PropertyService.Get ("MonoDevelop.Debugger.UseNewTreeView", true); protected ObjectValueTreeViewController controller; protected ObjectValueTreeView tree; - readonly ScrolledWindow scrolled; + readonly Control control; + PadFontChanger fontChanger; + StackFrame lastFrame; bool needsUpdateValues; bool needsUpdateFrame; bool initialResume; - StackFrame lastFrame; - PadFontChanger fontChanger; + bool disposed; public override Control Control { - get { - return scrolled; - } + get { return control; } } - public ObjectValuePad () + public ObjectValuePad (bool allowWatchExpressions = false) { - scrolled = new ScrolledWindow (); - scrolled.HscrollbarPolicy = PolicyType.Automatic; - scrolled.VscrollbarPolicy = PolicyType.Automatic; - if (UseNewTreeView) { - controller = new ObjectValueTreeViewController (); + controller = new ObjectValueTreeViewController (allowWatchExpressions); controller.AllowEditing = true; - var treeView = controller.GetControl () as GtkObjectValueTreeView; - fontChanger = new PadFontChanger (treeView, treeView.SetCustomFont, treeView.QueueResize); + if (Platform.IsMac) { + LoggingService.LogInfo ("Using MacObjectValueTreeView for {0}", allowWatchExpressions ? "Watch Pad" : "Locals Pad"); + var treeView = controller.GetMacControl (); + + fontChanger = new PadFontChanger (treeView, treeView.SetCustomFont, treeView.QueueResize); + + var scrolled = new AppKit.NSScrollView { + DocumentView = treeView, + AutohidesScrollers = false, + HasVerticalScroller = true, + HasHorizontalScroller = true, + }; + + // disable implicit animations + scrolled.WantsLayer = true; + scrolled.Layer.Actions = new NSDictionary ( + "actions", NSNull.Null, + "contents", NSNull.Null, + "hidden", NSNull.Null, + "onLayout", NSNull.Null, + "onOrderIn", NSNull.Null, + "onOrderOut", NSNull.Null, + "position", NSNull.Null, + "sublayers", NSNull.Null, + "transform", NSNull.Null, + "bounds", NSNull.Null); + + var host = new GtkNSViewHost (scrolled); + host.ShowAll (); + + control = host; + } else { + LoggingService.LogInfo ("Using GtkObjectValueTreeView for {0}", allowWatchExpressions ? "Watch Pad" : "Locals Pad"); + var treeView = controller.GetGtkControl (); + treeView.Show (); + + fontChanger = new PadFontChanger (treeView, treeView.SetCustomFont, treeView.QueueResize); - scrolled.Add (treeView); + var scrolled = new ScrolledWindow { + HscrollbarPolicy = PolicyType.Automatic, + VscrollbarPolicy = PolicyType.Automatic + }; + scrolled.Add (treeView); + scrolled.Show (); + + control = scrolled; + } } else { + LoggingService.LogInfo ("Using old ObjectValueTreeView for {0}", allowWatchExpressions ? "Watch Pad" : "Locals Pad"); tree = new ObjectValueTreeView (); + tree.AllowAdding = allowWatchExpressions; tree.AllowEditing = true; - tree.AllowAdding = false; + tree.Show (); fontChanger = new PadFontChanger (tree, tree.SetCustomFont, tree.QueueResize); + var scrolled = new ScrolledWindow { + HscrollbarPolicy = PolicyType.Automatic, + VscrollbarPolicy = PolicyType.Automatic + }; scrolled.Add (tree); - } + scrolled.Show (); - scrolled.ShowAll (); + control = scrolled; + } DebuggingService.CurrentFrameChanged += OnFrameChanged; DebuggingService.PausedEvent += OnDebuggerPaused; @@ -99,17 +145,23 @@ namespace MonoDevelop.Debugger public override void Dispose () { - if (fontChanger == null) + if (disposed) return; - fontChanger.Dispose (); - fontChanger = null; + if (fontChanger != null) { + fontChanger.Dispose (); + fontChanger = null; + } + + disposed = true; + DebuggingService.CurrentFrameChanged -= OnFrameChanged; DebuggingService.PausedEvent -= OnDebuggerPaused; DebuggingService.ResumedEvent -= OnDebuggerResumed; DebuggingService.StoppedEvent -= OnDebuggerStopped; DebuggingService.EvaluationOptionsChanged -= OnEvaluationOptionsChanged; DebuggingService.VariableChanged -= OnVariableChanged; + base.Dispose (); } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatch.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatch.cs index 679d5f4d0f..b30ad78980 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatch.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatch.cs @@ -40,7 +40,16 @@ namespace MonoDevelop.Debugger [ItemProperty] int line; - + + [ItemProperty] + int column; + + [ItemProperty] + int endLine; + + [ItemProperty] + int endColumn; + [ItemProperty (DefaultValue = 0)] int offsetX; @@ -113,6 +122,42 @@ namespace MonoDevelop.Debugger set { line = value; NotifyChanged (); } } + public int Column { + get { return column; } + set { column = value; NotifyChanged (); } + } + + public int EndLine { + get { return endLine; } + set { endLine = value; NotifyChanged (); } + } + + public int EndColumn { + get { return endColumn; } + set { endColumn = value; NotifyChanged (); } + } + + public PinnedWatchLocation Location { + get { return new PinnedWatchLocation (File, Line, Column, EndLine, EndColumn); } + set { + if (value != null) { + file = value.FileName; + line = value.Line; + column = value.Column; + endLine = value.EndLine; + endColumn = value.EndColumn; + } else { + file = null; + line = 0; + column = 0; + endLine = 0; + endColumn = 0; + } + + NotifyChanged (); + } + } + public int OffsetX { get { return offsetX; } set { offsetX = value; NotifyChanged (); } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatchLocation.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatchLocation.cs new file mode 100644 index 0000000000..3835c5ec86 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/PinnedWatchLocation.cs @@ -0,0 +1,65 @@ +// +// PinnedWatchLocation.cs +// +// Author: +// Jeffrey Stedfast +// +// Copyright (c) 2019 Microsoft Corp. +// +// 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. + +namespace MonoDevelop.Debugger +{ + public class PinnedWatchLocation + { + public PinnedWatchLocation (string fileName) + { + FileName = fileName; + } + + public PinnedWatchLocation (string fileName, int line, int column, int endLine, int endColumn) + { + FileName = fileName; + Line = line; + Column = column; + EndLine = endLine; + EndColumn = endColumn; + } + + public string FileName { + get; private set; + } + + public int Line { + get; set; + } + + public int Column { + get; set; + } + + public int EndLine { + get; set; + } + + public int EndColumn { + get; set; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/WatchPad.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/WatchPad.cs index ebba78d808..4437da61ec 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/WatchPad.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/WatchPad.cs @@ -42,15 +42,12 @@ namespace MonoDevelop.Debugger new Gtk.TargetEntry ("text/plain;charset=utf-8", Gtk.TargetFlags.App, 0) }; readonly List expressions = new List (); - - public WatchPad () + + public WatchPad () : base (true) { - if (UseNewTreeView) { - controller.AllowWatchExpressions = true; - } else { + if (!UseNewTreeView) { tree.EnableModelDragDest (DropTargets, Gdk.DragAction.Copy); tree.DragDataReceived += HandleDragDataReceived; - tree.AllowAdding = true; } } @@ -74,6 +71,8 @@ namespace MonoDevelop.Debugger public void AddWatch (string expression) { + LoggingService.LogInfo ("Adding expression '{0}'", expression); + if (UseNewTreeView) { controller.AddExpression (expression); } else { @@ -84,8 +83,8 @@ namespace MonoDevelop.Debugger void ReloadValues () { // clone the list of expressions -// expressions.Clear (); -// expressions.AddRange (controller.GetExpressions()); + expressions.Clear (); + expressions.AddRange (controller.GetExpressions ()); // remove the expressions because we're going to rebuild them controller.ClearAll (); diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/DebugValueTooltipProvider.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/DebugValueTooltipProvider.cs index 1cec611cda..4fc65a8da2 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/DebugValueTooltipProvider.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/DebugValueTooltipProvider.cs @@ -24,7 +24,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -// + +// Note: This API is only used by the old (Gtk) TextEditor. +// The new TextEditor uses MonoDevelop.Debugger.VSTextView.QuickInfo.DebuggerQuickInfoSourceProvider using System; using System.Threading; @@ -39,6 +41,7 @@ using MonoDevelop.Ide.Editor; namespace MonoDevelop.SourceEditor { + [Obsolete ("This has been replaced by MonoDevelop.Debugger.VSTextView.QuickInfo.DebuggerQuickInfoSourceProvider")] class DebugValueTooltipProvider: TooltipProvider { DebugValueWindow tooltip; @@ -78,13 +81,14 @@ namespace MonoDevelop.SourceEditor if (!DebuggingService.IsPaused) return null; - StackFrame frame = DebuggingService.CurrentFrame; + var frame = DebuggingService.CurrentFrame; if (frame == null) return null; var ed = CompileErrorTooltipProvider.GetExtensibleTextEditor (editor); if (ed == null) return null; + string expression = null; int startOffset; @@ -129,7 +133,15 @@ namespace MonoDevelop.SourceEditor public override Window CreateTooltipWindow (TextEditor editor, DocumentContext ctx, TooltipItem item, int offset, Xwt.ModifierKeys modifierState) { - var window = new DebugValueWindow ((Gtk.Window)(editor.GetNativeWidget ()).Toplevel, editor.FileName, editor.OffsetToLocation (offset).Line, DebuggingService.CurrentFrame, (ObjectValue)item.Item, null); + var position = editor.OffsetToLocation (offset); + var location = new PinnedWatchLocation (editor.FileName) { + Line = position.Line, + Column = position.Column, + EndLine = position.Line, + EndColumn = position.Column + }; + + var window = new DebugValueWindow ((Gtk.Window)(editor.GetNativeWidget ()).Toplevel, location, DebuggingService.CurrentFrame, (ObjectValue)item.Item, null); IdeApp.CommandService.RegisterTopWindow (window); return window; } diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/PinnedWatchWidget.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/PinnedWatchWidget.cs index 1f4cd28845..8372c179cb 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/PinnedWatchWidget.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/PinnedWatchWidget.cs @@ -89,7 +89,7 @@ namespace MonoDevelop.SourceEditor controller = new ObjectValueTreeViewController (); controller.AllowEditing = true; - treeView = (TreeView) controller.GetControl (headersVisible: false, compactView: true, allowPinning: true); + treeView = (TreeView) controller.GetGtkControl (headersVisible: false, compactView: true, allowPinning: true); controller.PinnedWatch = watch; valueTree = null; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/PopoverWindow.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/PopoverWindow.cs index 0fb6d6b324..413da95e39 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/PopoverWindow.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/PopoverWindow.cs @@ -587,6 +587,20 @@ namespace MonoDevelop.Components return retval; } + + protected override void OnShown () + { + base.OnShown (); + #if MAC + if (Core.Platform.IsMac && (Type == Gtk.WindowType.Popup || TypeHint == WindowTypeHint.PopupMenu || TypeHint == WindowTypeHint.Tooltip)) { + var wndnative = Mac.GtkMacInterop.GetNSWindow (this); + // the native window level is initially NSWindowLevel.PopUpMenu, but + // for some reason it gets resetted to NSWindowLevel.Normal after the window + // has been shown, so reset it back: + wndnative.Level = AppKit.NSWindowLevel.PopUpMenu; + } + #endif + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/PadFontChanger.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/PadFontChanger.cs index c88b74afbc..7d11eb4474 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/PadFontChanger.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/PadFontChanger.cs @@ -35,7 +35,7 @@ namespace MonoDevelop.Ide.Gui public sealed class PadFontChanger : IDisposable { - Gtk.Widget styleSource; + Control styleSource; Action updater; Action resizer; -- cgit v1.2.3