aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs
diff options
context:
space:
mode:
authorVictoria Plitt <Victoria.Plitt@twine-s.com>2019-04-08 13:49:55 +0300
committerVictoria Plitt <Victoria.Plitt@twine-s.com>2019-04-08 13:49:55 +0300
commitfc8a05358a92cc3c77c5f1e30d536807ef0614fd (patch)
treec65f696ebd60f3790145721307c255e5a339923f /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs
parentb4a71931ea52636c6b36376aa9d71697ccf73524 (diff)
downloadTango-fc8a05358a92cc3c77c5f1e30d536807ef0614fd.tar.gz
Tango-fc8a05358a92cc3c77c5f1e30d536807ef0614fd.zip
were added scripting projects
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs417
1 files changed, 417 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs
new file mode 100644
index 000000000..7766d323a
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs
@@ -0,0 +1,417 @@
+// 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.Linq;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Threading;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+using System.ComponentModel;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// Base class for completion windows. Handles positioning the window at the caret.
+ /// </summary>
+ public class CompletionWindowBase : Window
+ {
+ static CompletionWindowBase()
+ {
+ WindowStyleProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(WindowStyle.None));
+ ShowActivatedProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
+ ShowInTaskbarProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
+ }
+
+ /// <summary>
+ /// Gets the parent TextArea.
+ /// </summary>
+ public TextArea TextArea { get; private set; }
+
+ Window parentWindow;
+ TextDocument document;
+
+ /// <summary>
+ /// Gets/Sets the start of the text range in which the completion window stays open.
+ /// This text portion is used to determine the text used to select an entry in the completion list by typing.
+ /// </summary>
+ public int StartOffset { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the end of the text range in which the completion window stays open.
+ /// This text portion is used to determine the text used to select an entry in the completion list by typing.
+ /// </summary>
+ public int EndOffset { get; set; }
+
+ /// <summary>
+ /// Gets whether the window was opened above the current line.
+ /// </summary>
+ protected bool IsUp { get; private set; }
+
+ /// <summary>
+ /// Creates a new CompletionWindowBase.
+ /// </summary>
+ public CompletionWindowBase(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.TextArea = textArea;
+
+ parentWindow = Window.GetWindow(TextArea);
+ this.Owner = parentWindow;
+ this.AddHandler(MouseUpEvent, new MouseButtonEventHandler(OnMouseUp), true);
+ }
+
+ public virtual void ShowCompletion()
+ {
+ StartOffset = EndOffset = this.TextArea.Caret.Offset;
+
+ AttachEvents();
+ SetPosition();
+ UpdatePosition();
+ Show();
+ }
+
+ public virtual void HideCompletion()
+ {
+ Debug.WriteLine("Hide Completion...");
+ DetachEvents();
+ Hide();
+ }
+
+ #region Event Handlers
+
+ void AttachEvents()
+ {
+ document = this.TextArea.Document;
+ if (document != null) {
+ document.Changing += textArea_Document_Changing;
+ }
+ // LostKeyboardFocus seems to be more reliable than PreviewLostKeyboardFocus - see SD-1729
+ this.TextArea.LostKeyboardFocus += TextAreaLostFocus;
+ this.TextArea.TextView.ScrollOffsetChanged += TextViewScrollOffsetChanged;
+ this.TextArea.DocumentChanged += TextAreaDocumentChanged;
+ if (parentWindow != null) {
+ parentWindow.LocationChanged += parentWindow_LocationChanged;
+ }
+
+ // close previous completion windows of same type
+ foreach (InputHandler x in this.TextArea.StackedInputHandlers.OfType<InputHandler>()) {
+ if (x.window.GetType() == this.GetType())
+ this.TextArea.PopStackedInputHandler(x);
+ }
+
+ myInputHandler = new InputHandler(this);
+ this.TextArea.PushStackedInputHandler(myInputHandler);
+ }
+
+ /// <summary>
+ /// Detaches events from the text area.
+ /// </summary>
+ protected virtual void DetachEvents()
+ {
+ if (document != null) {
+ document.Changing -= textArea_Document_Changing;
+ }
+ this.TextArea.LostKeyboardFocus -= TextAreaLostFocus;
+ this.TextArea.TextView.ScrollOffsetChanged -= TextViewScrollOffsetChanged;
+ this.TextArea.DocumentChanged -= TextAreaDocumentChanged;
+ if (parentWindow != null) {
+ parentWindow.LocationChanged -= parentWindow_LocationChanged;
+ }
+ this.TextArea.PopStackedInputHandler(myInputHandler);
+ }
+
+ #region InputHandler
+ InputHandler myInputHandler;
+
+ /// <summary>
+ /// A dummy input handler (that justs invokes the default input handler).
+ /// This is used to ensure the completion window closes when any other input handler
+ /// becomes active.
+ /// </summary>
+ sealed class InputHandler : TextAreaStackedInputHandler
+ {
+ internal readonly CompletionWindowBase window;
+
+ public InputHandler(CompletionWindowBase window)
+ : base(window.TextArea)
+ {
+ Debug.Assert(window != null);
+ this.window = window;
+ }
+
+ public override void Detach()
+ {
+ base.Detach();
+ //window.HideCompletion();
+ }
+
+ const Key KeyDeadCharProcessed = (Key)0xac; // Key.DeadCharProcessed; // new in .NET 4
+
+ public override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ // prevents crash when typing deadchar while CC window is open
+ if (e.Key == KeyDeadCharProcessed)
+ return;
+ e.Handled = RaiseEventPair(window, PreviewKeyDownEvent, KeyDownEvent,
+ new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
+ }
+
+ public override void OnPreviewKeyUp(KeyEventArgs e)
+ {
+ if (e.Key == KeyDeadCharProcessed)
+ return;
+ e.Handled = RaiseEventPair(window, PreviewKeyUpEvent, KeyUpEvent,
+ new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
+ }
+ }
+ #endregion
+
+ void TextViewScrollOffsetChanged(object sender, EventArgs e)
+ {
+ // Workaround for crash #1580 (reproduction steps unknown):
+ // NullReferenceException in System.Windows.Window.CreateSourceWindow()
+ if (!sourceIsInitialized)
+ return;
+
+ IScrollInfo scrollInfo = this.TextArea.TextView;
+ Rect visibleRect = new Rect(scrollInfo.HorizontalOffset, scrollInfo.VerticalOffset, scrollInfo.ViewportWidth, scrollInfo.ViewportHeight);
+ // close completion window when the user scrolls so far that the anchor position is leaving the visible area
+ if (visibleRect.Contains(visualLocation) || visibleRect.Contains(visualLocationTop))
+ UpdatePosition();
+ else
+ HideCompletion();
+
+ }
+
+ void TextAreaDocumentChanged(object sender, EventArgs e)
+ {
+ HideCompletion();
+ }
+
+ void TextAreaLostFocus(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
+ }
+
+ void parentWindow_LocationChanged(object sender, EventArgs e)
+ {
+ UpdatePosition();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnDeactivated(EventArgs e)
+ {
+ base.OnDeactivated(e);
+ Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
+ }
+ #endregion
+
+ /// <summary>
+ /// Raises a tunnel/bubble event pair for a WPF control.
+ /// </summary>
+ /// <param name="target">The WPF control for which the event should be raised.</param>
+ /// <param name="previewEvent">The tunneling event.</param>
+ /// <param name="event">The bubbling event.</param>
+ /// <param name="args">The event args to use.</param>
+ /// <returns>The <see cref="RoutedEventArgs.Handled"/> value of the event args.</returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
+ protected static bool RaiseEventPair(UIElement target, RoutedEvent previewEvent, RoutedEvent @event, RoutedEventArgs args)
+ {
+ if (target == null)
+ throw new ArgumentNullException("target");
+ if (previewEvent == null)
+ throw new ArgumentNullException("previewEvent");
+ if (@event == null)
+ throw new ArgumentNullException("event");
+ if (args == null)
+ throw new ArgumentNullException("args");
+ args.RoutedEvent = previewEvent;
+ target.RaiseEvent(args);
+ args.RoutedEvent = @event;
+ target.RaiseEvent(args);
+ return args.Handled;
+ }
+
+ // Special handler: handledEventsToo
+ void OnMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ ActivateParentWindow();
+ }
+
+ /// <summary>
+ /// Activates the parent window.
+ /// </summary>
+ protected virtual void ActivateParentWindow()
+ {
+ if (parentWindow != null)
+ parentWindow.Activate();
+ }
+
+ void CloseIfFocusLost()
+ {
+ if (CloseOnFocusLost) {
+ Debug.WriteLine("CloseIfFocusLost: this.IsActive=" + this.IsActive + " IsTextAreaFocused=" + IsTextAreaFocused);
+ if (!this.IsActive && !IsTextAreaFocused) {
+ HideCompletion();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the completion window should automatically close when the text editor looses focus.
+ /// </summary>
+ protected virtual bool CloseOnFocusLost {
+ get { return true; }
+ }
+
+ bool IsTextAreaFocused {
+ get {
+ if (parentWindow != null && !parentWindow.IsActive)
+ return false;
+ return this.TextArea.IsKeyboardFocused;
+ }
+ }
+
+ bool sourceIsInitialized;
+
+ /// <inheritdoc/>
+ protected override void OnSourceInitialized(EventArgs e)
+ {
+ base.OnSourceInitialized(e);
+ SetPosition();
+
+ //sourceIsInitialized = true;
+ }
+
+ public void SetPosition()
+ {
+ if (document != null && this.StartOffset != this.TextArea.Caret.Offset)
+ {
+ SetPosition(new TextViewPosition(document.GetLocation(this.StartOffset)));
+ }
+ else
+ {
+ SetPosition(this.TextArea.Caret.Position);
+ }
+ }
+
+ public void UpdatePositionFix()
+ {
+ SetPosition(this.TextArea.Caret.Position);
+ }
+
+ //public void UpdatePosition()
+ //{
+ // SetPosition(this.TextArea.Caret.Position);
+ //}
+
+ /// <inheritdoc/>
+ protected override void OnClosed(EventArgs e)
+ {
+ DetachEvents();
+ HideCompletion();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+ if (!e.Handled && e.Key == Key.Escape) {
+ e.Handled = true;
+ HideCompletion();
+ }
+ }
+
+ Point visualLocation, visualLocationTop;
+
+ /// <summary>
+ /// Positions the completion window at the specified position.
+ /// </summary>
+ protected void SetPosition(TextViewPosition position)
+ {
+ TextView textView = this.TextArea.TextView;
+
+ visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom);
+ visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop);
+ UpdatePosition();
+ }
+
+ /// <summary>
+ /// Updates the position of the CompletionWindow based on the parent TextView position and the screen working area.
+ /// It ensures that the CompletionWindow is completely visible on the screen.
+ /// </summary>
+ public void UpdatePosition()
+ {
+ TextView textView = this.TextArea.TextView;
+ // PointToScreen returns device dependent units (physical pixels)
+ Point location = textView.PointToScreen(visualLocation - textView.ScrollOffset);
+ Point locationTop = textView.PointToScreen(visualLocationTop - textView.ScrollOffset);
+
+ // Let's use device dependent units for everything
+ Size completionWindowSize = new Size(this.ActualWidth, this.ActualHeight).TransformToDevice(textView);
+ Rect bounds = new Rect(location, completionWindowSize);
+ Rect workingScreen = System.Windows.Forms.Screen.GetWorkingArea(location.ToSystemDrawing()).ToWpf();
+ if (!workingScreen.Contains(bounds)) {
+ if (bounds.Left < workingScreen.Left) {
+ bounds.X = workingScreen.Left;
+ } else if (bounds.Right > workingScreen.Right) {
+ bounds.X = workingScreen.Right - bounds.Width;
+ }
+ if (bounds.Bottom > workingScreen.Bottom) {
+ bounds.Y = locationTop.Y - bounds.Height;
+ IsUp = true;
+ } else {
+ IsUp = false;
+ }
+ if (bounds.Y < workingScreen.Top) {
+ bounds.Y = workingScreen.Top;
+ }
+ }
+ // Convert the window bounds to device independent units
+ bounds = bounds.TransformFromDevice(textView);
+ this.Left = bounds.X;
+ this.Top = bounds.Y;
+
+ Debug.WriteLine($"Position: {Left}x{Top}");
+ }
+
+ /// <inheritdoc/>
+ protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
+ {
+ base.OnRenderSizeChanged(sizeInfo);
+ if (sizeInfo.HeightChanged && IsUp) {
+ this.Top += sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height;
+ }
+ }
+
+ /// <summary>
+ /// 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)
+ {
+ if (e.Offset + e.RemovalLength == this.StartOffset && e.RemovalLength > 0) {
+ HideCompletion(); // removal immediately in front of completion segment: close the window
+ // this is necessary when pressing backspace after dot-completion
+ }
+ if (e.Offset == StartOffset && e.RemovalLength == 0 && ExpectInsertionBeforeStart) {
+ StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion);
+ this.ExpectInsertionBeforeStart = false;
+ } else {
+ StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.BeforeInsertion);
+ }
+ EndOffset = e.GetNewOffset(EndOffset, AnchorMovementType.AfterInsertion);
+ }
+ }
+}