diff --git a/src/AddIns/Debugger/Debugger.AddIn/Tooltips/DebuggerTooltipControl.xaml.cs b/src/AddIns/Debugger/Debugger.AddIn/Tooltips/DebuggerTooltipControl.xaml.cs index 995387b0e7..75c8dd77e2 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Tooltips/DebuggerTooltipControl.xaml.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Tooltips/DebuggerTooltipControl.xaml.cs @@ -73,7 +73,7 @@ namespace Debugger.AddIn.Tooltips } } - bool ITooltip.CloseOnHoverEnd { + bool ITooltip.CloseWhenMouseMovesAway { get { return this.ChildTooltip == null; } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs index 931687437b..c9b5606026 100755 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs @@ -64,7 +64,9 @@ namespace ICSharpCode.AvalonEdit.AddIn this.MouseHover += TextEditorMouseHover; this.MouseHoverStopped += TextEditorMouseHoverStopped; + this.MouseMove += TextEditorMouseMove; this.MouseLeave += TextEditorMouseLeave; + this.Unloaded += OnUnloaded; this.TextArea.TextView.MouseDown += TextViewMouseDown; this.TextArea.Caret.PositionChanged += HighlightBrackets; this.TextArea.TextView.VisualLinesChanged += CodeEditorView_VisualLinesChanged; @@ -240,6 +242,11 @@ namespace ICSharpCode.AvalonEdit.AddIn void TextEditorMouseHover(object sender, MouseEventArgs e) { Debug.Assert(sender == this); + + if (!TryCloseExistingPopup(false)) { + return; + } + ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(this.Adapter); var pos = this.TextArea.TextView.GetPositionFloor(e.GetPosition(this.TextArea.TextView) + this.TextArea.TextView.ScrollOffset); args.InDocument = pos.HasValue; @@ -252,21 +259,19 @@ namespace ICSharpCode.AvalonEdit.AddIn ToolTipRequestService.RequestToolTip(args); } - if (!TryCloseExistingPopup(false)) { - return; - } - if (args.ContentToShow != null) { popupToolTip = args.ContentToShow as Popup; if (popupToolTip != null) { var popupPosition = GetPopupPosition(e); + popupToolTip.Closed += ToolTipClosed; popupToolTip.HorizontalOffset = popupPosition.X; popupToolTip.VerticalOffset = popupPosition.Y; popupToolTip.StaysOpen = true; // We will close it ourselves e.Handled = true; popupToolTip.IsOpen = true; + distanceToPopupLimit = double.PositiveInfinity; // reset limit; we'll re-calculate it on the next mouse movement } else { if (toolTip == null) { toolTip = new ToolTip(); @@ -293,7 +298,7 @@ namespace ICSharpCode.AvalonEdit.AddIn bool TryCloseExistingPopup(bool mouseClick) { if (popupToolTip != null) { - if (popupToolTip.IsOpen && !mouseClick && popupToolTip is ITooltip && !((ITooltip)popupToolTip).CloseOnHoverEnd) { + if (popupToolTip.IsOpen && !mouseClick && popupToolTip is ITooltip && !((ITooltip)popupToolTip).CloseWhenMouseMovesAway) { return false; // Popup does not want to be closed yet } popupToolTip.IsOpen = false; @@ -324,12 +329,47 @@ namespace ICSharpCode.AvalonEdit.AddIn void TextEditorMouseHoverStopped(object sender, MouseEventArgs e) { + // Non-popup tooltips get closed as soon as the mouse starts moving again if (toolTip != null) { toolTip.IsOpen = false; e.Handled = true; } } + double distanceToPopupLimit; + const double MaxMovementAwayFromPopup = 5; + + void TextEditorMouseMove(object sender, MouseEventArgs e) + { + if (popupToolTip != null) { + double distanceToPopup = GetDistanceToPopup(e); + if (distanceToPopup > distanceToPopupLimit) { + // Close popup if mouse moved away, exceeding the limit + TryCloseExistingPopup(false); + } else { + // reduce distanceToPopupLimit + distanceToPopupLimit = Math.Min(distanceToPopupLimit, distanceToPopup + MaxMovementAwayFromPopup); + } + } + } + + double GetDistanceToPopup(MouseEventArgs e) + { + Point p = popupToolTip.Child.PointFromScreen(PointToScreen(e.GetPosition(this))); + Size size = popupToolTip.Child.RenderSize; + double x = 0; + if (p.X < 0) + x = -p.X; + else if (p.X > size.Width) + x = p.X - size.Width; + double y = 0; + if (p.Y < 0) + y = -p.Y; + else if (p.Y > size.Height) + y = p.Y - size.Height; + return Math.Sqrt(x * x + y * y); + } + void TextEditorMouseLeave(object sender, MouseEventArgs e) { if (popupToolTip != null && !popupToolTip.IsMouseOver) { @@ -337,10 +377,25 @@ namespace ICSharpCode.AvalonEdit.AddIn TryCloseExistingPopup(false); } } + + void OnUnloaded(object sender, EventArgs e) + { + // Close popup when another document gets selected + // TextEditorMouseLeave is not sufficient for this because the mouse might be over the popup when the document switch happens (e.g. Ctrl+Tab) + TryCloseExistingPopup(true); + } - void ToolTipClosed(object sender, RoutedEventArgs e) + void ToolTipClosed(object sender, EventArgs e) { - toolTip = null; + if (toolTip == sender) { + toolTip = null; + } + if (popupToolTip == sender) { + // Because popupToolTip instances are created by the tooltip provider, + // they might be reused; so we should detach the event handler + popupToolTip.Closed -= ToolTipClosed; + popupToolTip = null; + } } #endregion diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/XmlDoc/XmlDocTooltipProvider.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/XmlDoc/XmlDocTooltipProvider.cs index 28e9732bf9..38774dca6a 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/XmlDoc/XmlDocTooltipProvider.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/XmlDoc/XmlDocTooltipProvider.cs @@ -6,6 +6,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Documents; +using System.Windows.Input; using System.Windows.Media; using ICSharpCode.AvalonEdit.AddIn.Options; using ICSharpCode.NRefactory.Semantics; @@ -56,8 +57,14 @@ namespace ICSharpCode.AvalonEdit.AddIn.XmlDoc document.FontSize = CodeEditorOptions.Instance.FontSize; } - public bool CloseOnHoverEnd { - get { return true; } + public bool CloseWhenMouseMovesAway { + get { return !this.IsKeyboardFocusWithin; } + } + + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnLostKeyboardFocus(e); + this.IsOpen = false; } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs index d52b47ed9d..97c252d5f1 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs @@ -247,7 +247,7 @@ namespace ICSharpCode.AvalonEdit.Editing // we re-throw them later to allow the application's unhandled exception handler // to catch them textArea.Dispatcher.BeginInvoke( - DispatcherPriority.Normal, + DispatcherPriority.Send, new Action(delegate { throw new DragDropException("Exception during drag'n'drop", ex); })); diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MemberLookup.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MemberLookup.cs index c1d5a9e432..2e0b8aa119 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MemberLookup.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Resolver/MemberLookup.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// /// Whether protected access to instance members is allowed. /// True if the type of the reference is derived from the current class. - /// Protected static members may be accessibe even if false is passed for this parameter. + /// Protected static members may be accessible even if false is passed for this parameter. /// public bool IsAccessible(IEntity entity, bool allowProtectedAccess) { diff --git a/src/Main/Base/Project/Editor/ITooltip.cs b/src/Main/Base/Project/Editor/ITooltip.cs index 5e338ff1d5..cdfb59ec61 100644 --- a/src/Main/Base/Project/Editor/ITooltip.cs +++ b/src/Main/Base/Project/Editor/ITooltip.cs @@ -9,6 +9,6 @@ namespace ICSharpCode.SharpDevelop.Editor public interface ITooltip { /// Should the tooltip close when the mouse moves away? - bool CloseOnHoverEnd { get; } + bool CloseWhenMouseMovesAway { get; } } }