aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs
blob: e566e6aa95c95c24c34bec787354ec9a58575ab3 (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
generated by cgit v1.3.1 (git 2.54.0) at 2026-07-19 05:35:27 +0000
 


/a>
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.TextFormatting;
using Tango.Scripting.Core;
using Tango.Scripting.Editors.Document;
using Tango.Scripting.Editors.Rendering;
using Tango.Scripting.Editors.Utils;

namespace Tango.Scripting.Editors.Editing
{
    public class BreakPointMargin : AbstractMargin, IWeakEventListener
    {
        private TextArea textArea;
        private int maxLineNumberLength = 1;
        private BitmapSource _arrowBitmap;
        private ScriptEditor _editor;

        public ObservableCollection<BreakPoint> BreakPoints { get; set; }

        public Brush Background
        {
            get { return (Brush)GetValue(BackgroundProperty); }
            set { SetValue(BackgroundProperty, value); }
        }
        public static readonly DependencyProperty BackgroundProperty =
            DependencyProperty.Register("Background", typeof(Brush), typeof(BreakPointMargin), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(50, 50, 50))));

        public Brush Foreground
        {
            get { return (Brush)GetValue(ForegroundProperty); }
            set { SetValue(ForegroundProperty, value); }
        }
        public static readonly DependencyProperty ForegroundProperty =
            DependencyProperty.Register("Foreground", typeof(Brush), typeof(BreakPointMargin), new PropertyMetadata(Brushes.Red));

        static BreakPointMargin()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(BreakPointMargin),
                                                     new FrameworkPropertyMetadata(typeof(BreakPointMargin)));
        }

        public BreakPointMargin(ScriptEditor editor)
        {
            _editor = editor;
            BreakPoints = new ObservableCollection<BreakPoint>();
            BreakPoints.CollectionChanged += BreakPoints_CollectionChanged;
            RenderOptions.SetEdgeMode(this, EdgeMode.Unspecified);

            _arrowBitmap = new BitmapImage(new Uri($"pack://application:,,,/Tango.Scripting.Editors;component/Images/break_point_arrow.png", UriKind.Absolute));
        }

        private void BreakPoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            InvalidateVisual();
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            return new Size(20, 0);
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            TextView textView = this.TextView;
            Size renderSize = this.RenderSize;
            if (textView != null && textView.VisualLinesValid)
            {
                drawingContext.DrawRectangle(Background, new Pen(Background, 1), new Rect(0, 0, ActualWidth, ActualHeight));

                var foreground = Foreground;
                foreach (VisualLine line in textView.VisualLines)
                {
                    int lineNumber = line.FirstDocumentLine.LineNumber;

                    BreakPoint b = BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber);

                    if (b != null)
                    {
                        double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
                        drawingContext.DrawEllipse(Foreground, new Pen(Brushes.Gainsboro, 1), new Point(10, y - textView.VerticalOffset + 8), 6, 6);

                        if (b.IsActive)
                        {
                            drawingContext.DrawImage(_arrowBitmap, new Rect(6, y - textView.VerticalOffset + 2.5, 8.5, 10));
                        }
                    }
                }
            }
        }

        protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)
        {
            if (oldTextView != null)
            {
                oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged;
            }
            base.OnTextViewChanged(oldTextView, newTextView);
            if (newTextView != null)
            {
                newTextView.VisualLinesChanged += TextViewVisualLinesChanged;

                // find the text area belonging to the new text view
                textArea = newTextView.Services.GetService(typeof(TextArea)) as TextArea;
            }
            else
            {
                textArea = null;
            }
            InvalidateVisual();
        }

        protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument)
        {
            if (oldDocument != null)
            {
                PropertyChangedEventManager.RemoveListener(oldDocument, this, "LineCount");
            }
            base.OnDocumentChanged(oldDocument, newDocument);
            if (newDocument != null)
            {
                PropertyChangedEventManager.AddListener(newDocument, this, "LineCount");
            }
            OnDocumentLineCountChanged();
        }

        protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (managerType == typeof(PropertyChangedEventManager))
            {
                OnDocumentLineCountChanged();
                return true;
            }
            return false;
        }

        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            return ReceiveWeakEvent(managerType, sender, e);
        }

        private void OnDocumentLineCountChanged()
        {
            int documentLineCount = Document != null ? Document.LineCount : 1;
            int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length;

            foreach (var breakPoint in BreakPoints.ToList())
            {
                if (breakPoint.LineNumber > documentLineCount)
                {
                    BreakPoints.Remove(breakPoint);
                }
                else
                {
                    try
                    {
                        var line = Document.GetLineByNumber(breakPoint.LineNumber);
                        if (line != null)
                        {
                            String lineText = Document.GetText(line.Offset, line.Length);
                            if (!IsBreakPointValid(lineText))
                            {
                                BreakPoints.Remove(breakPoint);
                            }
                        }
                    }
                    catch { }
                }
            }

            // The margin looks too small when there is only one digit, so always reserve space for
            // at least two digits
            if (newLength < 2)
                newLength = 2;

            if (newLength != maxLineNumberLength)
            {
                maxLineNumberLength = newLength;
                InvalidateMeasure();
            }
        }

        private void TextViewVisualLinesChanged(object sender, EventArgs e)
        {
            InvalidateVisual();
        }

        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            // accept clicks even when clicking on the background
            return new PointHitTestResult(this, hitTestParameters.HitPoint);
        }

        private VisualLine GetLineNumberByMousePosition(MouseEventArgs e)
        {
            Point pos = e.GetPosition(TextView);
            pos.X = 0;
            pos.Y += TextView.VerticalOffset;
            VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y);
            return vl;
        }

        private bool IsBreakPointValid(String lineText)
        {
            if (lineText.EndsWith(";") && !lineText.StartsWith("using"))
            {
                return true;
            }

            return false;
        }

        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            base.OnPreviewMouseMove(e);

            if (_editor.DisableBreakPoints)
            {
                Cursor = Cursors.No;
            }
            else
            {
                Cursor = Cursors.Arrow;
            }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            if (_editor.DisableBreakPoints)
            {
                return;
            }

            try
            {
                if (!e.Handled && TextView != null && textArea != null)
                {
                    e.Handled = true;
                    textArea.Focus();

                    var visualLine = GetLineNumberByMousePosition(e);

                    int? lineNumber = visualLine != null ? (int?)visualLine.FirstDocumentLine.LineNumber : null;

                    if (lineNumber != null)
                    {
                        var breakPoint = BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber.Value);
                        if (breakPoint != null)
                        {
                            BreakPoints.Remove(breakPoint);
                        }
                        else
                        {
                            var lineText = Document.GetText(visualLine.FirstDocumentLine.Offset, visualLine.FirstDocumentLine.Length).Trim();

                            if (IsBreakPointValid(lineText))
                            {
                                BreakPoint newBreakPoint = new BreakPoint();
                                newBreakPoint.LineNumber = lineNumber.Value;
                                BreakPoints.Add(newBreakPoint);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }
    }
}