aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Win32.cs
blob: 421c35c5a5e45bcbc935910d9dc88071defce97d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)

using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

namespace Tango.Scripting.Editors.Utils
{
	/// <summary>
	/// Wrapper around Win32 functions.
	/// </summary>
	static class Win32
	{
		/// <summary>
		/// Gets the caret blink time.
		/// </summary>
		public static TimeSpan CaretBlinkTime {
			get { return TimeSpan.FromMilliseconds(SafeNativeMethods.GetCaretBlinkTime()); }
		}
		
		/// <summary>
		/// Creates an invisible Win32 caret for the specified Visual with the specified size (coordinates local to the owner visual).
		/// </summary>
		public static bool CreateCaret(Visual owner, Size size)
		{
			if (owner == null)
				throw new ArgumentNullException("owner");
			HwndSource source = PresentationSource.FromVisual(owner) as HwndSource;
			if (source != null) {
				Vector r = owner.PointToScreen(new Point(size.Width, size.Height)) - owner.PointToScreen(new Point(0, 0));
				return SafeNativeMethods.CreateCaret(source.Handle, IntPtr.Zero, (int)Math.Ceiling(r.X), (int)Math.Ceiling(r.Y));
			} else {
				return false;
			}
		}
		
		/// <summary>
		/// Sets the position of the caret previously created using <see cref="CreateCaret"/>. position is relative to the owner visual.
		/// </summary>
		public static bool SetCaretPosition(Visual owner, Point position)
		{
			if (owner == null)
				throw new ArgumentNullException("owner");
			HwndSource source = PresentationSource.FromVisual(owner) as HwndSource;
			if (source != null) {
				Point pointOnRootVisual = owner.TransformToAncestor(source.RootVisual).Transform(position);
				Point pointOnHwnd = pointOnRootVisual.TransformToDevice(source.RootVisual);
				return SafeNativeMethods.SetCaretPos((int)pointOnHwnd.X, (int)pointOnHwnd.Y);
			} else {
				return false;
			}
		}
		
		/// <summary>
		/// Destroys the caret previously created using <see cref="CreateCaret"/>.
		/// </summary>
		public static bool DestroyCaret()
		{
			return SafeNativeMethods.DestroyCaret();
		}
		
		[SuppressUnmanagedCodeSecurity]
		static class SafeNativeMethods
		{
			[DllImport("user32.dll")]
			public static extern int GetCaretBlinkTime();
			
			[DllImport("user32.dll")]
			[return: MarshalAs(UnmanagedType.Bool)]
			public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);
			
			[DllImport("user32.dll")]
			[return: MarshalAs(UnmanagedType.Bool)]
			public static extern bool SetCaretPos(int x, int y);
			
			[DllImport("user32.dll")]
			[return: MarshalAs(UnmanagedType.Bool)]
			public static extern bool DestroyCaret();
		}
	}
}
an class="k">readonly TextView textView; readonly CaretLayer caretAdorner; bool visible; internal Caret(TextArea textArea) { this.textArea = textArea; this.textView = textArea.TextView; position = new TextViewPosition(1, 1, 0); caretAdorner = new CaretLayer(textView); textView.InsertLayer(caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace); textView.VisualLinesChanged += TextView_VisualLinesChanged; textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged; } void TextView_VisualLinesChanged(object sender, EventArgs e) { if (visible) { Show(); } // required because the visual columns might have changed if the // element generators did something differently than on the last run // (e.g. a FoldingSection was collapsed) InvalidateVisualColumn(); } void TextView_ScrollOffsetChanged(object sender, EventArgs e) { if (caretAdorner != null) { caretAdorner.InvalidateVisual(); } } double desiredXPos = double.NaN; TextViewPosition position; /// <summary> /// Gets/Sets the position of the caret. /// Retrieving this property will validate the visual column (which can be expensive). /// Use the <see cref="Location"/> property instead if you don't need the visual column. /// </summary> public TextViewPosition Position { get { ValidateVisualColumn(); return position; } set { if (position != value) { position = value; storedCaretOffset = -1; //Debug.WriteLine("Caret position changing to " + value); ValidatePosition(); InvalidateVisualColumn(); RaisePositionChanged(); Log("Caret position changed to " + value); if (visible) Show(); } } } /// <summary> /// Gets the caret position without validating it. /// </summary> internal TextViewPosition NonValidatedPosition { get { return position; } } /// <summary> /// Gets/Sets the location of the caret. /// The getter of this property is faster than <see cref="Position"/> because it doesn't have /// to validate the visual column. /// </summary> public TextLocation Location { get { return position.Location; } set { this.Position = new TextViewPosition(value); } } /// <summary> /// Gets/Sets the caret line. /// </summary> public int Line { get { return position.Line; } set { this.Position = new TextViewPosition(value, position.Column); } } /// <summary> /// Gets/Sets the caret column. /// </summary> public int Column { get { return position.Column; } set { this.Position = new TextViewPosition(position.Line, value); } } /// <summary> /// Gets/Sets the caret visual column. /// </summary> public int VisualColumn { get { ValidateVisualColumn(); return position.VisualColumn; } set { this.Position = new TextViewPosition(position.Line, position.Column, value); } } bool isInVirtualSpace; /// <summary> /// Gets whether the caret is in virtual space. /// </summary> public bool IsInVirtualSpace { get { ValidateVisualColumn(); return isInVirtualSpace; } } int storedCaretOffset; internal void OnDocumentChanging() { storedCaretOffset = this.Offset; InvalidateVisualColumn(); } internal void OnDocumentChanged(DocumentChangeEventArgs e) { InvalidateVisualColumn(); if (storedCaretOffset >= 0) { int newCaretOffset = e.GetNewOffset(storedCaretOffset, AnchorMovementType.Default); TextDocument document = textArea.Document; if (document != null) { // keep visual column this.Position = new TextViewPosition(document.GetLocation(newCaretOffset), position.VisualColumn); } } storedCaretOffset = -1; } /// <summary> /// Gets/Sets the caret offset. /// Setting the caret offset has the side effect of setting the <see cref="DesiredXPos"/> to NaN. /// </summary> public int Offset { get { TextDocument document = textArea.Document; if (document == null) { return 0; } else { return document.GetOffset(position.Location); } } set { TextDocument document = textArea.Document; if (document != null) { this.Position = new TextViewPosition(document.GetLocation(value)); this.DesiredXPos = double.NaN; } } } /// <summary> /// Gets/Sets the desired x-position of the caret, in device-independent pixels. /// This property is NaN if the caret has no desired position. /// </summary> public double DesiredXPos { get { return desiredXPos; } set { desiredXPos = value; } } void ValidatePosition() { if (position.Line < 1) position.Line = 1; if (position.Column < 1) position.Column = 1; if (position.VisualColumn < -1) position.VisualColumn = -1; TextDocument document = textArea.Document; if (document != null) { if (position.Line > document.LineCount) { position.Line = document.LineCount; position.Column = document.GetLineByNumber(position.Line).Length + 1; position.VisualColumn = -1; } else { DocumentLine line = document.GetLineByNumber(position.Line); if (position.Column > line.Length + 1) { position.Column = line.Length + 1; position.VisualColumn = -1; } } } } /// <summary> /// Event raised when the caret position has changed. /// If the caret position is changed inside a document update (between BeginUpdate/EndUpdate calls), /// the PositionChanged event is raised only once at the end of the document update. /// </summary> public event EventHandler PositionChanged; bool raisePositionChangedOnUpdateFinished; void RaisePositionChanged() { if (textArea.Document != null && textArea.Document.IsInUpdate) { raisePositionChangedOnUpdateFinished = true; } else { if (PositionChanged != null) { PositionChanged(this, EventArgs.Empty); } } } internal void OnDocumentUpdateFinished() { if (raisePositionChangedOnUpdateFinished) { if (PositionChanged != null) { PositionChanged(this, EventArgs.Empty); } } } bool visualColumnValid; void ValidateVisualColumn() { if (!visualColumnValid) { TextDocument document = textArea.Document; if (document != null) { //Debug.WriteLine("Explicit validation of caret column"); var documentLine = document.GetLineByNumber(position.Line); RevalidateVisualColumn(textView.GetOrConstructVisualLine(documentLine)); } } } void InvalidateVisualColumn() { visualColumnValid = false; } /// <summary> /// Validates the visual column of the caret using the specified visual line. /// The visual line must contain the caret offset. /// </summary> void RevalidateVisualColumn(VisualLine visualLine) { if (visualLine == null) throw new ArgumentNullException("visualLine"); // mark column as validated visualColumnValid = true; int caretOffset = textView.Document.GetOffset(position.Location); int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset; position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace); // search possible caret positions int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); // If position.VisualColumn was valid, we're done with validation. if (newVisualColumnForwards != position.VisualColumn) { // also search backwards so that we can pick the better match int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0) throw ThrowUtil.NoValidCaretPosition(); // determine offsets for new visual column positions int newOffsetForwards; if (newVisualColumnForwards >= 0) newOffsetForwards = visualLine.GetRelativeOffset(newVisualColumnForwards) + firstDocumentLineOffset; else newOffsetForwards = -1; int newOffsetBackwards; if (newVisualColumnBackwards >= 0) newOffsetBackwards = visualLine.GetRelativeOffset(newVisualColumnBackwards) + firstDocumentLineOffset; else newOffsetBackwards = -1; int newVisualColumn, newOffset; // if there's only one valid position, use it if (newVisualColumnForwards < 0) { newVisualColumn = newVisualColumnBackwards; newOffset = newOffsetBackwards; } else if (newVisualColumnBackwards < 0) { newVisualColumn = newVisualColumnForwards; newOffset = newOffsetForwards; } else { // two valid positions: find the better match if (Math.Abs(newOffsetBackwards - caretOffset) < Math.Abs(newOffsetForwards - caretOffset)) { // backwards is better newVisualColumn = newVisualColumnBackwards; newOffset = newOffsetBackwards; } else { // forwards is better newVisualColumn = newVisualColumnForwards; newOffset = newOffsetForwards; } } this.Position = new TextViewPosition(textView.Document.GetLocation(newOffset), newVisualColumn); } isInVirtualSpace = (position.VisualColumn > visualLine.VisualLength); } Rect CalcCaretRectangle(VisualLine visualLine) { if (!visualColumnValid) { RevalidateVisualColumn(visualLine); } TextLine textLine = visualLine.GetTextLine(position.VisualColumn); double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn); double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop); double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom); return new Rect(xPos, lineTop, SystemParameters.CaretWidth, lineBottom - lineTop); } /// <summary> /// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document. /// </summary> public Rect CalculateCaretRectangle() { if (textView != null && textView.Document != null) { VisualLine visualLine = textView.GetOrConstructVisualLine(textView.Document.GetLineByNumber(position.Line)); return CalcCaretRectangle(visualLine); } else { return Rect.Empty; } } /// <summary> /// Minimum distance of the caret to the view border. /// </summary> internal const double MinimumDistanceToViewBorder = 30; /// <summary> /// Scrolls the text view so that the caret is visible. /// </summary> public void BringCaretToView() { BringCaretToView(MinimumDistanceToViewBorder); } internal void BringCaretToView(double border) { Rect caretRectangle = CalculateCaretRectangle(); if (!caretRectangle.IsEmpty) { caretRectangle.Inflate(border, border); textView.MakeVisible(caretRectangle); } } /// <summary> /// Makes the caret visible and updates its on-screen position. /// </summary> public void Show() { Log("Caret.Show()"); visible = true; if (!showScheduled) { showScheduled = true; textArea.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(ShowInternal)); } } bool showScheduled; bool hasWin32Caret; void ShowInternal() { showScheduled = false; // if show was scheduled but caret hidden in the meantime if (!visible) return; if (caretAdorner != null && textView != null) { VisualLine visualLine = textView.GetVisualLine(position.Line); if (visualLine != null) { Rect caretRect = CalcCaretRectangle(visualLine); // Create Win32 caret so that Windows knows where our managed caret is. This is necessary for // features like 'Follow text editing' in the Windows Magnifier. if (!hasWin32Caret) { hasWin32Caret = Win32.CreateCaret(textView, caretRect.Size); } if (hasWin32Caret) { Win32.SetCaretPosition(textView, caretRect.Location - textView.ScrollOffset); } caretAdorner.Show(caretRect); textArea.ime.UpdateCompositionWindow(); } else { caretAdorner.Hide(); } } } /// <summary> /// Makes the caret invisible. /// </summary> public void Hide() { Log("Caret.Hide()"); visible = false; if (hasWin32Caret) { Win32.DestroyCaret(); hasWin32Caret = false; } if (caretAdorner != null) { caretAdorner.Hide(); } } [Conditional("DEBUG")] static void Log(string text) { // commented out to make debug output less noisy - add back if there are any problems with the caret //Debug.WriteLine(text); } /// <summary> /// Gets/Sets the color of the caret. /// </summary> public Brush CaretBrush { get { return caretAdorner.CaretBrush; } set { caretAdorner.CaretBrush = value; } } } }