From 71993014c910924fe0db9881570ede7ce10f421b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald <daniel@danielgrunwald.de> Date: Tue, 24 Mar 2009 21:28:26 +0000 Subject: [PATCH] AvalonEdit: tooltips. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3913 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../AvalonEdit.AddIn/Src/CodeEditor.cs | 41 ++++- .../Src/SharpDevelopCompletionWindow.cs | 4 +- .../Src/ToolTips/ResourceToolTipProvider.cs | 24 ++- .../CodeCompletion/CompletionList.cs | 9 ++ .../CodeCompletion/CompletionWindow.cs | 46 +++++- .../Document/LineNode.cs | 3 + .../Document/TextAnchor.cs | 2 +- .../Gui/CaretNavigationCommandHandler.cs | 1 + .../Gui/TextAreaDefaultInputHandlers.cs | 24 ++- .../ICSharpCode.AvalonEdit/Gui/TextEditor.cs | 72 +++++++++ .../ICSharpCode.AvalonEdit/Gui/TextView.cs | 153 ++++++++++++++++++ .../documentation/Introduction.xml | 6 +- .../documentation/SyntaxHighlighting.xml | 4 +- .../documentation/TextRendering.xml | 18 ++- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Src/Services/Debugger/DebuggerService.cs | 120 +++----------- .../Gui/Editor/ITextAreaToolTipProvider.cs | 48 +----- .../Project/Src/TextEditor/ToolTipService.cs | 103 ++++++++++++ 18 files changed, 499 insertions(+), 180 deletions(-) create mode 100644 src/Main/Base/Project/Src/TextEditor/ToolTipService.cs diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index 25c9d281a7..d4958808c8 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -5,12 +5,16 @@ // <version>$Revision$</version> // </file> -using ICSharpCode.AvalonEdit.CodeCompletion; using System; using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; + +using ICSharpCode.AvalonEdit.CodeCompletion; using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor; namespace ICSharpCode.AvalonEdit.AddIn @@ -30,6 +34,41 @@ namespace ICSharpCode.AvalonEdit.AddIn this.FontFamily = new FontFamily("Consolas"); this.FontSize = 13; this.TextArea.TextEntered += TextArea_TextInput; + this.MouseHover += CodeEditor_MouseHover; + this.MouseHoverStopped += CodeEditor_MouseHoverStopped; + } + + ToolTip toolTip; + + void CodeEditor_MouseHover(object sender, MouseEventArgs e) + { + ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(textEditorAdapter); + var pos = GetPositionFromPoint(e.GetPosition(this)); + args.InDocument = pos.HasValue; + if (pos.HasValue) { + args.LogicalPosition = AvalonEditDocumentAdapter.ToLocation(pos.Value); + } + ToolTipRequestService.RequestToolTip(args); + if (args.ContentToShow != null) { + if (toolTip == null) { + toolTip = new ToolTip(); + toolTip.Closed += toolTip_Closed; + } + toolTip.Content = args.ContentToShow; + toolTip.IsOpen = true; + } + } + + void CodeEditor_MouseHoverStopped(object sender, MouseEventArgs e) + { + if (toolTip != null) { + toolTip.IsOpen = false; + } + } + + void toolTip_Closed(object sender, RoutedEventArgs e) + { + toolTip = null; } volatile static ReadOnlyCollection<ICodeCompletionBinding> codeCompletionBindings; diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs index 9edf23dac9..357303d0c6 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs @@ -71,7 +71,9 @@ namespace ICSharpCode.AvalonEdit.AddIn } public object Description { - get { return item.Description; } + get { + return item.Description; + } } public ImageSource Image { diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs index 257ae4f2ba..e611f632dc 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs @@ -5,13 +5,14 @@ // <version>$Revision$</version> // </file> -using ICSharpCode.SharpDevelop.Refactoring; +using ICSharpCode.SharpDevelop.Dom.Refactoring; using System; using System.Drawing; using Hornung.ResourceToolkit.Resolver; +using ICSharpCode.NRefactory; +using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor; -using ICSharpCode.TextEditor; -using ICSharpCode.TextEditor.Document; +using ICSharpCode.SharpDevelop.Refactoring; namespace Hornung.ResourceToolkit.ToolTips { @@ -20,22 +21,19 @@ namespace Hornung.ResourceToolkit.ToolTips /// </summary> public class ResourceToolTipProvider : ITextAreaToolTipProvider { - - public ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e) + public void HandleToolTipRequest(ToolTipRequestEventArgs e) { - TextLocation logicPos = e.LogicalPosition; - IDocument doc = textArea.Document; - if (logicPos.X > doc.GetLineSegment(logicPos.Y).Length - 1) { - return null; + Location logicPos = e.LogicalPosition; + IDocument doc = e.Editor.Document; + if (logicPos.X > doc.GetLine(logicPos.Y).Length) { + return; } - ResourceResolveResult result = ResourceResolverService.Resolve(textArea.MotherTextEditorControl.FileName, new TextEditorDocument(doc), logicPos.Y, logicPos.X, null); + ResourceResolveResult result = ResourceResolverService.Resolve(e.Editor.FileName, doc, logicPos.Y - 1, logicPos.X - 1, null); if (result != null && result.ResourceFileContent != null) { - return new ToolTipInfo(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key)); + e.ShowToolTip(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key)); } - - return null; } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs index 3844497bdb..eff46a9cf3 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; @@ -140,6 +141,14 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion } } + /// <summary> + /// Occurs when the SelectedItem property changes. + /// </summary> + public event SelectionChangedEventHandler SelectionChanged { + add { AddHandler(Selector.SelectionChangedEvent, value); } + remove { RemoveHandler(Selector.SelectionChangedEvent, value); } + } + /// <summary> /// Selects the item that starts with the specified text. /// </summary> diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs index 3621775b38..cf71707520 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs @@ -9,6 +9,7 @@ using System; using System.Diagnostics; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; @@ -27,6 +28,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion int startOffset; int endOffset; readonly CompletionList completionList = new CompletionList(); + ToolTip toolTip = new ToolTip(); /// <summary> /// Creates a new code completion window. @@ -38,11 +40,41 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion this.Width = 200; this.Content = completionList; + toolTip.PlacementTarget = this; + toolTip.Placement = PlacementMode.Right; + toolTip.Closed += toolTip_Closed; + startOffset = endOffset = this.TextArea.Caret.Offset; document = textArea.TextView.Document; completionList.InsertionRequested += completionList_InsertionRequested; + completionList.SelectionChanged += completionList_SelectionChanged; } - + + #region ToolTip handling + void toolTip_Closed(object sender, RoutedEventArgs e) + { + // Clear content after tooltip is closed. + // We cannot clear is immediately when setting IsOpen=false + // because the tooltip uses an animation for closing. + if (toolTip != null) + toolTip.Content = null; + } + + void completionList_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var item = completionList.SelectedItem; + if (item == null) + return; + object description = item.Description; + if (description != null) { + toolTip.Content = description; + toolTip.IsOpen = true; + } else { + toolTip.IsOpen = false; + } + } + #endregion + void completionList_InsertionRequested(object sender, EventArgs e) { var item = completionList.SelectedItem; @@ -134,6 +166,16 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion } #endregion + /// <inheritdoc/> + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + if (toolTip != null) { + toolTip.IsOpen = false; + toolTip = null; + } + } + /// <inheritdoc/> protected override void OnKeyDown(KeyEventArgs e) { @@ -167,6 +209,8 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion /// Gets/sets whether the completion window should expect text insertion at the start offset, /// which not go into the completion region, but before it. /// </summary> + /// <remarks>This property allows only a single insertion, it is reset to false + /// when that insertion has occurred.</remarks> public bool ExpectInsertionBeforeStart { get; set; } void textArea_Document_Changing(object sender, DocumentChangeEventArgs e) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs index e43eb464c9..c4ed992444 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs @@ -31,6 +31,9 @@ namespace ICSharpCode.AvalonEdit.Document // apparently the JIT only optimizes for memory when there are at least three small fields. // Currently, DocumentLine takes 40 bytes on x86 (8 byte object overhead, 4 pointers, 3 ints, and another DWORD // for the small fields). + // TODO: a possible optimization would be to combine 'totalLength' and the small fields into a single uint. + // delimiterSize takes only two bits, the two bools take another two bits; so there's still + // 28 bits left for totalLength. 268435455 characters per line should be enough for everyone :) /// <summary> /// Resets the line to enable its reuse after a document rebuild. diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs index ee46778532..15356e9173 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs @@ -115,7 +115,7 @@ namespace ICSharpCode.AvalonEdit.Document /// </summary> public TextLocation Location { get { - return new TextLocation(Line, Column); + return document.GetLocation(this.Offset); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs index 5bc31815d6..0b3bb5f077 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs @@ -108,6 +108,7 @@ namespace ICSharpCode.AvalonEdit.Gui { TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { + args.Handled = true; textArea.Caret.Offset = textArea.Document.TextLength; textArea.Selection = new SimpleSelection(0, textArea.Document.TextLength); textArea.Caret.BringCaretToView(); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs index 17c7252df0..240934101f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs @@ -57,27 +57,39 @@ namespace ICSharpCode.AvalonEdit.Gui void ExecuteUndo(object sender, ExecutedRoutedEventArgs e) { var undoStack = GetUndoStack(); - if (undoStack != null && undoStack.CanUndo) - undoStack.Undo(); + if (undoStack != null) { + if (undoStack.CanUndo) + undoStack.Undo(); + e.Handled = true; + } } void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e) { var undoStack = GetUndoStack(); - e.CanExecute = undoStack != null && undoStack.CanUndo; + if (undoStack != null) { + e.Handled = true; + e.CanExecute = undoStack.CanUndo; + } } void ExecuteRedo(object sender, ExecutedRoutedEventArgs e) { var undoStack = GetUndoStack(); - if (undoStack != null && undoStack.CanRedo) - undoStack.Redo(); + if (undoStack != null) { + if (undoStack.CanRedo) + undoStack.Redo(); + e.Handled = true; + } } void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e) { var undoStack = GetUndoStack(); - e.CanExecute = undoStack != null && undoStack.CanRedo; + if (undoStack != null) { + e.Handled = true; + e.CanExecute = undoStack.CanRedo; + } } #endregion } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs index b1dedb6d70..fdc9ca14be 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs @@ -731,5 +731,77 @@ namespace ICSharpCode.AvalonEdit } } #endregion + + /// <summary> + /// Gets the text view position from a point inside the editor. + /// </summary> + /// <param name="p">The position, relative to top left + /// corner of TextEditor control</param> + /// <returns>The text view position, or null if the point is outside the document.</returns> + public TextViewPosition? GetPositionFromPoint(Point p) + { + if (this.Document == null) + return null; + TextView textView = this.TextArea.TextView; + return textView.GetPosition(TranslatePoint(p, textView) + textView.ScrollOffset); + } + + /// <summary> + /// The PreviewMouseHover event. + /// </summary> + public static readonly RoutedEvent PreviewMouseHoverEvent = + TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); + + /// <summary> + /// The MouseHover event. + /// </summary> + public static readonly RoutedEvent MouseHoverEvent = + TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); + + + /// <summary> + /// The PreviewMouseHoverStopped event. + /// </summary> + public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = + TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + /// <summary> + /// The MouseHoverStopped event. + /// </summary> + public static readonly RoutedEvent MouseHoverStoppedEvent = + TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + + /// <summary> + /// Occurs when the mouse has hovered over a fixed location for some time. + /// </summary> + public event MouseEventHandler PreviewMouseHover { + add { AddHandler(PreviewMouseHoverEvent, value); } + remove { RemoveHandler(PreviewMouseHoverEvent, value); } + } + + /// <summary> + /// Occurs when the mouse has hovered over a fixed location for some time. + /// </summary> + public event MouseEventHandler MouseHover { + add { AddHandler(MouseHoverEvent, value); } + remove { RemoveHandler(MouseHoverEvent, value); } + } + + /// <summary> + /// Occurs when the mouse had previously hovered but now started moving again. + /// </summary> + public event MouseEventHandler PreviewMouseHoverStopped { + add { AddHandler(PreviewMouseHoverStoppedEvent, value); } + remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } + } + + /// <summary> + /// Occurs when the mouse had previously hovered but now started moving again. + /// </summary> + public event MouseEventHandler MouseHoverStopped { + add { AddHandler(MouseHoverStoppedEvent, value); } + remove { RemoveHandler(MouseHoverStoppedEvent, value); } + } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs index 96c8362b72..a564faa2b2 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs @@ -1176,6 +1176,8 @@ namespace ICSharpCode.AvalonEdit.Gui /// </summary> public VisualLine GetVisualLineFromVisualTop(double visualTop) { + // TODO: change this method to also work outside the visible range - + // required to make GetPosition work as expected! EnsureVisualLines(); foreach (VisualLine vl in this.VisualLines) { if (visualTop < vl.VisualTop) @@ -1224,6 +1226,25 @@ namespace ICSharpCode.AvalonEdit.Gui } return visualLine.GetVisualPosition(visualColumn, yPositionMode); } + + /// <summary> + /// Gets the text view position from the specified visual position. + /// </summary> + /// <param name="visualPosition">The position in WPF device-independent pixels relative + /// to the top left corner of the document.</param> + /// <returns>The logical position, or null if the position is outside the document.</returns> + public TextViewPosition? GetPosition(Point visualPosition) + { + VerifyAccess(); + if (this.Document == null) + throw new InvalidOperationException("There is no document assigned to the TextView"); + VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y); + if (line == null) + return null; + int visualColumn = line.GetVisualColumn(visualPosition); + int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; + return new TextViewPosition(document.GetLocation(documentOffset), visualColumn); + } #endregion #region Service Provider @@ -1256,6 +1277,138 @@ namespace ICSharpCode.AvalonEdit.Gui } #endregion + #region MouseHover + /// <summary> + /// The PreviewMouseHover event. + /// </summary> + public static readonly RoutedEvent PreviewMouseHoverEvent = + EventManager.RegisterRoutedEvent("PreviewMouseHover", RoutingStrategy.Tunnel, + typeof(MouseEventHandler), typeof(TextView)); + /// <summary> + /// The MouseHover event. + /// </summary> + public static readonly RoutedEvent MouseHoverEvent = + EventManager.RegisterRoutedEvent("MouseHover", RoutingStrategy.Bubble, + typeof(MouseEventHandler), typeof(TextView)); + + /// <summary> + /// The PreviewMouseHoverStopped event. + /// </summary> + public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = + EventManager.RegisterRoutedEvent("PreviewMouseHoverStopped", RoutingStrategy.Tunnel, + typeof(MouseEventHandler), typeof(TextView)); + /// <summary> + /// The MouseHoverStopped event. + /// </summary> + public static readonly RoutedEvent MouseHoverStoppedEvent = + EventManager.RegisterRoutedEvent("MouseHoverStopped", RoutingStrategy.Bubble, + typeof(MouseEventHandler), typeof(TextView)); + + + /// <summary> + /// Occurs when the mouse has hovered over a fixed location for some time. + /// </summary> + public event MouseEventHandler PreviewMouseHover { + add { AddHandler(PreviewMouseHoverEvent, value); } + remove { RemoveHandler(PreviewMouseHoverEvent, value); } + } + + /// <summary> + /// Occurs when the mouse has hovered over a fixed location for some time. + /// </summary> + public event MouseEventHandler MouseHover { + add { AddHandler(MouseHoverEvent, value); } + remove { RemoveHandler(MouseHoverEvent, value); } + } + + /// <summary> + /// Occurs when the mouse had previously hovered but now started moving again. + /// </summary> + public event MouseEventHandler PreviewMouseHoverStopped { + add { AddHandler(PreviewMouseHoverStoppedEvent, value); } + remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } + } + + /// <summary> + /// Occurs when the mouse had previously hovered but now started moving again. + /// </summary> + public event MouseEventHandler MouseHoverStopped { + add { AddHandler(MouseHoverStoppedEvent, value); } + remove { RemoveHandler(MouseHoverStoppedEvent, value); } + } + + DispatcherTimer mouseHoverTimer; + Point mouseHoverStartPoint; + MouseEventArgs mouseHoverLastEventArgs; + bool mouseHovering; + + /// <inheritdoc/> + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + Point newPosition = e.GetPosition(this); + Vector mouseMovement = mouseHoverStartPoint - newPosition; + if (Math.Abs(mouseMovement.X) > SystemParameters.MouseHoverWidth + || Math.Abs(mouseMovement.Y) > SystemParameters.MouseHoverHeight) + { + StopHovering(); + mouseHoverStartPoint = newPosition; + mouseHoverLastEventArgs = e; + mouseHoverTimer = new DispatcherTimer(SystemParameters.MouseHoverTime, DispatcherPriority.Background, + OnMouseHoverTimerElapsed, this.Dispatcher); + mouseHoverTimer.Start(); + } + // do not set e.Handled - allow others to also handle MouseMove + } + + /// <inheritdoc/> + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); + StopHovering(); + // do not set e.Handled - allow others to also handle MouseLeave + } + + void StopHovering() + { + if (mouseHoverTimer != null) { + mouseHoverTimer.Stop(); + mouseHoverTimer = null; + } + if (mouseHovering) { + mouseHovering = false; + RaiseHoverEventPair(PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent); + } + } + + void OnMouseHoverTimerElapsed(object sender, EventArgs e) + { + mouseHoverTimer.Stop(); + mouseHoverTimer = null; + + mouseHovering = true; + RaiseHoverEventPair(PreviewMouseHoverEvent, MouseHoverEvent); + } + + void RaiseHoverEventPair(RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent) + { + var mouseDevice = mouseHoverLastEventArgs.MouseDevice; + var stylusDevice = mouseHoverLastEventArgs.StylusDevice; + int inputTime = Environment.TickCount; + var args1 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) { + RoutedEvent = tunnelingEvent, + Source = this + }; + RaiseEvent(args1); + var args2 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) { + RoutedEvent = bubblingEvent, + Source = this, + Handled = args1.Handled + }; + RaiseEvent(args2); + } + #endregion + /// <summary> /// Collapses lines for the purpose of scrolling. This method is meant for /// <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml index e3ef6e742f..6521aa0c05 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml @@ -11,10 +11,10 @@ <para>While the WPF RichTextBox is quite powerful, you quickly run into its limits when trying to use it as a code editor: it's hard to write efficient syntax highlighting for it, and you cannot really implement features like code folding with the standard RichTextBox.</para> - <para>The issue is that in the RichTextBox edits a rich document. In contrast, AvalonEdit - just edits text. However, AvalonEdit offers lots of possibilities on how the text document is + <para>The issue is simple: the RichTextBox edits a rich document. In contrast, AvalonEdit + simply edits text. However, AvalonEdit offers lots of possibilities on how the text document is displayed - so it is much more suitable for a code editor where things like the text color - are not controlled by the user, but simply depend on the text (syntax highlighting).</para> + are not controlled by the user, but instead depend on the text (syntax highlighting).</para> </introduction> <!-- TODO: screenshot--> diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml index b8f85b7733..42c9f55b5e 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml @@ -9,8 +9,8 @@ Text Rendering extension points is the support for "visual line transformers" that can change the display of a visual line after it has been constructed by the "visual element generators". A useful base class implementing IVisualLineTransformer for the purpose of syntax highlighting - is DocumentColorizingTransformer. But please take a look at that class' documentation to see - how to write fully custom syntax highlighters. This article discusses the XML-driven built-in + is DocumentColorizingTransformer. Take a look at that class' documentation to see + how to write fully custom syntax highlighters. This article only discusses the XML-driven built-in highlighting engine. </para> </introduction> diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml index 6e666bb793..124bf61a3c 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml @@ -22,8 +22,9 @@ <content> <para> VisualLines are only created for the visible part of the document. - This usually happens in the MeasureOverride of TextView: - when the TextView is measured, it uses the height tree to determine the first + Lots of actions can trigger their creation, but most commonly the creation will be + caused by the MeasureOverride method of TextView. + When the TextView is measured, it uses the height tree to determine the first document line in the visible region. Then, it constructs and measures a VisualLine for that first line, and repeats that with the following lines until the visible region is filled. @@ -52,10 +53,10 @@ one redraw for multiple input actions. </para> <para> - ALERT: this means you need to ensure that if you access VisualLine, you can cope - with the case that the VisualLines you're interested in are not available. + ALERT: this means that if you access VisualLine, you must be able to handle + the case that your requested VisualLines are not available. You can use GetOrConstructVisualLine to make the text editor re-build the visual line - if it does not exist anymore; or you can call EnsureVisualLines() to make the text + if it does not exist anymore; or you can call EnsureVisualLines() to make the text view create all VisualLines in the visible region. </para> </content> @@ -68,6 +69,13 @@ room in between the elements returned from the generators is filled with text elements. Then, the VisualLine assigns the VisualColumn and RelativeTextOffset properties of the line elements. </para> + <para> + For example, a line contains the text "Hello, World". + The user has enabled "ShowSpaces", so the text editor should show a little dot instead of the space. + In this case, the SingleCharacterElementGenerator, which is responsible for ShowSpaces, will produce + a "SpaceTextElement" for the space character. Because no other generators are interested in the line, + the remaining strings "Hello," and "World" will be represented by VisualLineText elements. + </para> </content> </section> <section> diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 1224ae5de7..d58c00d46e 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -558,6 +558,7 @@ <Compile Include="Src\TextEditor\ICompletionItem.cs" /> <Compile Include="Src\TextEditor\ICompletionItemList.cs" /> <Compile Include="Src\TextEditor\ITextEditor.cs" /> + <Compile Include="Src\TextEditor\ToolTipService.cs" /> <Compile Include="Src\TextEditor\XmlFormattingStrategy.cs" /> <Compile Include="Src\Services\Tasks\TaskEventHandler.cs" /> <Compile Include="Src\TextEditor\Commands\NavigationCommands.cs" /> diff --git a/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs b/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs index b00f742831..07a6e14cd5 100644 --- a/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs +++ b/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs @@ -5,13 +5,13 @@ // <version>$Revision$</version> // </file> +using ICSharpCode.NRefactory; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.Text; using System.Windows.Forms; - using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Gui; @@ -21,7 +21,6 @@ using ICSharpCode.TextEditor.Document; using BM = ICSharpCode.SharpDevelop.Bookmarks; using ITextAreaToolTipProvider = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ITextAreaToolTipProvider; using ITextEditorControlProvider = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ITextEditorControlProvider; -using ToolTipInfo = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ToolTipInfo; namespace ICSharpCode.SharpDevelop.Debugging { @@ -37,7 +36,6 @@ namespace ICSharpCode.SharpDevelop.Debugging ClearDebugMessages(); }; - WorkbenchSingleton.WorkbenchCreated += new EventHandler(WorkspaceCreated); BM.BookmarkManager.Added += BookmarkAdded; BM.BookmarkManager.Removed += BookmarkRemoved; } @@ -240,33 +238,13 @@ namespace ICSharpCode.SharpDevelop.Debugging } } - static void WorkspaceCreated(object sender, EventArgs args) - { - WorkbenchSingleton.Workbench.ViewOpened += new ViewContentEventHandler(ViewContentOpened); - WorkbenchSingleton.Workbench.ViewClosed += new ViewContentEventHandler(ViewContentClosed); - } - + /* TODO: reimplement this stuff static void ViewContentOpened(object sender, ViewContentEventArgs e) { - if (e.Content is ITextEditorControlProvider) { - TextArea textArea = ((ITextEditorControlProvider)e.Content).TextEditorControl.ActiveTextAreaControl.TextArea; - textArea.IconBarMargin.MouseDown += IconBarMouseDown; textArea.ToolTipRequest += TextAreaToolTipRequest; textArea.MouseLeave += TextAreaMouseLeave; - } - } - - static void ViewContentClosed(object sender, ViewContentEventArgs e) - { - if (e.Content is ITextEditorControlProvider) { - TextArea textArea = ((ITextEditorControlProvider)e.Content).TextEditorControl.ActiveTextAreaControl.TextArea; - - textArea.IconBarMargin.MouseDown -= IconBarMouseDown; - textArea.ToolTipRequest -= TextAreaToolTipRequest; - textArea.MouseLeave -= TextAreaMouseLeave; - } - } + }*/ public static void RemoveCurrentLineMarker() { @@ -292,59 +270,8 @@ namespace ICSharpCode.SharpDevelop.Debugging } #region Tool tips - const string ToolTipProviderAddInTreePath = "/SharpDevelop/ViewContent/DefaultTextEditor/ToolTips"; - static DebuggerGridControl oldToolTipControl; - /// <summary> - /// This function shows variable values as tooltips - /// </summary> - static void TextAreaToolTipRequest(object sender, ToolTipRequestEventArgs e) - { - DebuggerGridControl toolTipControl = null; - try { - TextArea textArea = (TextArea)sender; - if (e.ToolTipShown) return; - if (oldToolTipControl != null && !oldToolTipControl.AllowClose) return; - if (!CodeCompletionOptions.EnableCodeCompletion) return; - if (!CodeCompletionOptions.TooltipsEnabled) return; - - if (CodeCompletionOptions.TooltipsOnlyWhenDebugging) { - if (currentDebugger == null) return; - if (!currentDebugger.IsDebugging) return; - } - - if (e.InDocument) { - // Query all registered tooltip providers using the AddInTree. - // The first one that does not return null will be used. - ToolTipInfo ti = null; - foreach (ITextAreaToolTipProvider toolTipProvider in AddInTree.BuildItems<ITextAreaToolTipProvider>(ToolTipProviderAddInTreePath, null, false)) { - if ((ti = toolTipProvider.GetToolTipInfo(textArea, e)) != null) { - break; - } - } - - if (ti != null) { - toolTipControl = ti.ToolTipControl as DebuggerGridControl; - if (ti.ToolTipText != null) { - e.ShowToolTip(ti.ToolTipText); - } - } - CloseOldToolTip(); - if (toolTipControl != null) { - toolTipControl.ShowForm(textArea, e.LogicalPosition); - } - oldToolTipControl = toolTipControl; - - } - } catch (Exception ex) { - ICSharpCode.Core.MessageService.ShowError(ex, "Error while requesting tooltip for location " + e.LogicalPosition); - } finally { - if (toolTipControl == null && CanCloseOldToolTip) - CloseOldToolTip(); - } - } - static bool CanCloseOldToolTip { get { return oldToolTipControl != null && oldToolTipControl.AllowClose; @@ -360,35 +287,28 @@ namespace ICSharpCode.SharpDevelop.Debugging } } - static void TextAreaMouseLeave(object source, EventArgs e) - { - if (CanCloseOldToolTip && !oldToolTipControl.IsMouseOver) - CloseOldToolTip(); - } - /// <summary> /// Gets debugger tooltip information for the specified position. /// A descriptive text for the element or a DebuggerGridControl /// showing its current value (when in debugging mode) can be returned /// through the ToolTipInfo object. - /// Returns <c>null</c>, if no tooltip information is available. /// </summary> - internal static ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e) + internal static void HandleToolTipRequest(ToolTipRequestEventArgs e) { - TextLocation logicPos = e.LogicalPosition; - IDocument doc = textArea.Document; - IExpressionFinder expressionFinder = ParserService.GetExpressionFinder(textArea.MotherTextEditorControl.FileName); + Location logicPos = e.LogicalPosition; + var doc = e.Editor.Document; + IExpressionFinder expressionFinder = ParserService.GetExpressionFinder(e.Editor.FileName); if (expressionFinder == null) - return null; - LineSegment seg = doc.GetLineSegment(logicPos.Y); - if (logicPos.X > seg.Length - 1) - return null; - string textContent = doc.TextContent; - ExpressionResult expressionResult = expressionFinder.FindFullExpression(textContent, seg.Offset + logicPos.X); + return; + var currentLine = doc.GetLine(logicPos.Y); + if (logicPos.X > currentLine.Length) + return; + string textContent = doc.Text; + ExpressionResult expressionResult = expressionFinder.FindFullExpression(textContent, doc.PositionToOffset(logicPos.Line, logicPos.Column)); string expression = (expressionResult.Expression ?? "").Trim(); if (expression.Length > 0) { // Look if it is variable - ResolveResult result = ParserService.Resolve(expressionResult, logicPos.Y + 1, logicPos.X + 1, textArea.MotherTextEditorControl.FileName, textContent); + ResolveResult result = ParserService.Resolve(expressionResult, logicPos.Y, logicPos.X, e.Editor.FileName, textContent); bool debuggerCanShowValue; string toolTipText = GetText(result, expression, out debuggerCanShowValue); if (Control.ModifierKeys == Keys.Control) { @@ -397,18 +317,18 @@ namespace ICSharpCode.SharpDevelop.Debugging } if (toolTipText != null) { if (debuggerCanShowValue && currentDebugger != null) { - return new ToolTipInfo(currentDebugger.GetTooltipControl(expressionResult.Expression)); + e.ShowToolTip(currentDebugger.GetTooltipControl(expressionResult.Expression)); + } else { + e.ShowToolTip(toolTipText); } - return new ToolTipInfo(toolTipText); } } else { #if DEBUG if (Control.ModifierKeys == Keys.Control) { - return new ToolTipInfo("no expr: " + expressionResult.ToString()); + e.ShowToolTip("no expr: " + expressionResult.ToString()); } #endif } - return null; } static string GetText(ResolveResult result, string expression, out bool debuggerCanShowValue) @@ -523,9 +443,9 @@ namespace ICSharpCode.SharpDevelop.Debugging /// </remarks> public class DebuggerTextAreaToolTipProvider : ITextAreaToolTipProvider { - public ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e) + public void HandleToolTipRequest(ToolTipRequestEventArgs e) { - return DebuggerService.GetToolTipInfo(textArea, e); + DebuggerService.HandleToolTipRequest(e); } } } diff --git a/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs b/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs index 2356917e16..74adb56087 100644 --- a/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs +++ b/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs @@ -22,52 +22,6 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor /// if available. /// </summary> /// <returns><c>null</c>, if no tooltip information is available at this position, otherwise a ToolTipInfo object containing the tooltip information to be displayed.</returns> - ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e); - } - - /// <summary> - /// Contains information about a tooltip to be shown on the text area. - /// </summary> - public class ToolTipInfo - { - object toolTipObject; - - /// <summary> - /// Gets the tool tip text to be displayed. - /// May be <c>null</c>. - /// </summary> - public string ToolTipText { - get { - return this.toolTipObject as string; - } - } - - /// <summary> - /// Gets the DebuggerGridControl to be shown as tooltip. - /// May be <c>null</c>. - /// </summary> - public Control ToolTipControl { - get { - return this.toolTipObject as Control; - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="ToolTipInfo"/> class. - /// </summary> - /// <param name="toolTipText">The tooltip text to be displayed.</param> - public ToolTipInfo(string toolTipText) - { - this.toolTipObject = toolTipText; - } - - /// <summary> - /// Initializes a new instance of the <see cref="ToolTipInfo"/> class. - /// </summary> - /// <param name="toolTipControl">The DebuggerGridControl to be shown as tooltip.</param> - public ToolTipInfo(Control toolTipControl) - { - this.toolTipObject = toolTipControl; - } + void HandleToolTipRequest(ToolTipRequestEventArgs e); } } diff --git a/src/Main/Base/Project/Src/TextEditor/ToolTipService.cs b/src/Main/Base/Project/Src/TextEditor/ToolTipService.cs new file mode 100644 index 0000000000..4f40f45941 --- /dev/null +++ b/src/Main/Base/Project/Src/TextEditor/ToolTipService.cs @@ -0,0 +1,103 @@ +// <file> +// <copyright see="prj:///doc/copyright.txt"/> +// <license see="prj:///doc/license.txt"/> +// <author name="Daniel Grunwald"/> +// <version>$Revision$</version> +// </file> + +using ICSharpCode.SharpDevelop.Debugging; +using System; +using ICSharpCode.Core; +using ICSharpCode.NRefactory; +using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor; + +namespace ICSharpCode.SharpDevelop +{ + /// <summary> + /// Static class for the ToolTipRequested event. + /// </summary> + public static class ToolTipRequestService + { + const string ToolTipProviderAddInTreePath = "/SharpDevelop/ViewContent/DefaultTextEditor/ToolTips"; + + /// <summary> + /// This event occurs on a tool tip request, + /// after any <see cref="ITextAreaToolTipProvider"/> registered in the addintree have run. + /// This event is still raised if AddIns handled it, so please check the Handled property. + /// </summary> + public static event EventHandler<ToolTipRequestEventArgs> ToolTipRequested; + + public static void RequestToolTip(ToolTipRequestEventArgs e) + { + if (e == null) + throw new ArgumentNullException("e"); + + if (!CodeCompletionOptions.EnableCodeCompletion) return; + if (!CodeCompletionOptions.TooltipsEnabled) return; + + if (CodeCompletionOptions.TooltipsOnlyWhenDebugging) { + if (!DebuggerService.IsDebuggerLoaded) return; + if (!DebuggerService.CurrentDebugger.IsDebugging) return; + } + + // Query all registered tooltip providers using the AddInTree. + // The first one that does not return null will be used. + foreach (ITextAreaToolTipProvider toolTipProvider in AddInTree.BuildItems<ITextAreaToolTipProvider>(ToolTipProviderAddInTreePath, null, false)) { + toolTipProvider.HandleToolTipRequest(e); + if (e.Handled) + break; + } + + EventHandler<ToolTipRequestEventArgs> eh = ToolTipRequested; + if (eh != null) + eh(null, e); + } + } + + public class ToolTipRequestEventArgs : EventArgs + { + /// <summary> + /// Gets whether the tool tip request was handled. + /// </summary> + public bool Handled { get; set; } + + /// <summary> + /// Gets the editor causing the request. + /// </summary> + public ITextEditor Editor { get; private set; } + + /// <summary> + /// Gets whether the mouse was inside the document bounds. + /// </summary> + public bool InDocument { get; set; } + + /// <summary> + /// The mouse position, in document coordinates. + /// </summary> + public Location LogicalPosition { get; set; } + + /// <summary> + /// Gets/Sets the content to show as a tooltip. + /// </summary> + public object ContentToShow { get; set; } + + /// <summary> + /// Shows the tool tip. + /// </summary> + public void ShowToolTip(object content) + { + if (content == null) + throw new ArgumentNullException("content"); + this.Handled = true; + this.ContentToShow = content; + } + + public ToolTipRequestEventArgs(ITextEditor editor) + { + if (editor == null) + throw new ArgumentNullException("editor"); + this.Editor = editor; + this.InDocument = true; + } + } +}