aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml
blob: 33755f90941597aeef613eb2e3b9a6f2ad3b78fc (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
86
<UserControl x:Class="Tango.MachineStudio.Common.Controls.RealTimeGraphControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
             xmlns:graphEx="clr-namespace:RealTimeGraphEx.FastGraphs;assembly=RealTimeGraphEx"
             xmlns:components="clr-namespace:RealTimeGraphEx.Components;assembly=RealTimeGraphEx"
             xmlns:converters="clr-namespace:Tango.MachineStudio.Common.Converters"
             xmlns:local="clr-namespace:Tango.MachineStudio.Common.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="150" d:DesignWidth="300">

    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!--RealTimeGraphEx-->
                <ResourceDictionary Source="pack://application:,,,/RealTimeGraphEx;component/Resources/Resources.xaml"></ResourceDictionary>
                <ResourceDictionary Source="../Resources/MaterialDesign.xaml"></ResourceDictionary>

				<ResourceDictionary>
					<Style TargetType="ContentControl" x:Key="graphContent">
						<Style.Setters>
							<Setter Property="ContentTemplate">
								<Setter.Value>
									<DataTemplate>
										<Grid MouseEnter="Graph_MouseEnter" MouseLeave="Graph_MouseLeave" ClipToBounds="True">
											<ContentControl Content="{Binding}"></ContentControl>
											<Grid Opacity="0.8" HorizontalAlignment="Stretch" VerticalAlignment="Top" ClipToBounds="True" Height="35" Margin="0 -35 0 0">
												<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right" VerticalAlignment="Top">
													<Button Margin="0 0 5 0" Click="OnGraphFullScreen" ToolTip="Full Screen" Width="24" Height="24" BorderBrush="Transparent" Background="{StaticResource AccentColorBrush}" Style="{StaticResource MaterialDesignFloatingActionAccentButton}" >
														<materialDesign:PackIcon Kind="Fullscreen" HorizontalAlignment="Right" Width="20" Height="20" Foreground="{StaticResource WhiteBrush}" />
													</Button>
													<Button Click="OnGraphRemove" ToolTip="Remove" Width="24" Height="24" BorderBrush="Transparent" Background="#FF7777" Style="{StaticResource MaterialDesignFloatingActionAccentButton}" >
														<materialDesign:PackIcon Kind="CloseCircle" HorizontalAlignment="Right" Width="20" Height="20" Foreground="{StaticResource WhiteBrush}" />
													</Button>
												</StackPanel>
											</Grid>
										</Grid>
									</DataTemplate>
								</Setter.Value>
							</Setter>
						</Style.Setters>
					</Style>
				</ResourceDictionary>
			</ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>

    <Grid>
        <!--Temperature-->
        <ContentControl Style="{StaticResource graphContent}" Margin="0 0 0 0" MinHeight="5">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="40"/>
                    <ColumnDefinition Width="438*"/>
                </Grid.ColumnDefinitions>

                <Border BorderBrush="{StaticResource AccentColorBrush}" BorderThickness="1 1 0 1" Background="#90FFFFFF">
                    <StackPanel Orientation="Horizontal">
                        <components:YAxisScroll x:Name="yAxis" Interval="6" Graph="{Binding ElementName=Graph}" Width="35" Foreground="{StaticResource MaterialDesignLightForeground}" VerticalOffset="-5" FontSize="8" StringFormat="#0.0"></components:YAxisScroll>
                        <components:YAxisTicks x:Name="yAxisTicks" SmallTickTemplate="{StaticResource graphTicksTemplate}" Width="5" SmallTicks="6" Foreground="{StaticResource MaterialDesignLightForeground}" BigTicks="10" Graph="{Binding ElementName=Graph}"></components:YAxisTicks>
                    </StackPanel>
                </Border>
                <Border Grid.Column="1" BorderThickness="1" BorderBrush="{StaticResource borderBrush}" Background="{DynamicResource graphBackground}" Margin="5 0 0 0">
                    <graphEx:RealTimeGraphExLineErase x:Name="Graph" x:FieldModifier="public" UseAutoRange="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=UseAutoRange}" Controller="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=Controller}" Antialiased="True" RefreshRate="30" MarkerColor="{StaticResource graphsMarkerColor}" FillGraph="False" Stroke="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=Color}" Minimum="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=Minimum,Mode=TwoWay}" Maximum="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=Maximum,Mode=TwoWay}">
                        <graphEx:RealTimeGraphExLineErase.Components>
                            <components:MouseValueToolTip ToolTipTemplate="{StaticResource graphTooltipTemplate}" />
                            <components:GridLines Rows="4" Columns="6" GridBrush="{DynamicResource graphGridLinesBrush}"></components:GridLines>
                        </graphEx:RealTimeGraphExLineErase.Components>
                        <graphEx:RealTimeGraphExLineErase.InnerContent>
                            <Grid>
                                <Label Style="{StaticResource graphLabel}">
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl,AncestorLevel=2},Path=SensorName,FallbackValue='Dispenser Motor'}"></TextBlock>
                                        <TextBlock Foreground="Gray" Margin="10 0 0 0" FontFamily="Sylfaen Regular" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl,AncestorLevel=2},Path=SensorUnits,FallbackValue='(hz)'}"></TextBlock>
                                    </StackPanel>
                                </Label>
                            </Grid>
                        </graphEx:RealTimeGraphExLineErase.InnerContent>
                    </graphEx:RealTimeGraphExLineErase>
                </Border>
            </Grid>
        </ContentControl>
    </Grid>
</UserControl>
#003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
// 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);
		}
	}
}