From 925bf377913f1003324d746b9e28e053186bfa29 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Mon, 27 Jul 2020 15:25:20 +0300 Subject: Procedures: Fixed issue with TextBox on DialogWindow. Fixed issue with multiple library linking. Implemented error checking. Many improvements. --- .../Contracts/IProcedureDesignerView.cs | 2 + .../Tango.FSE.Procedures/DialogController.cs | 31 +- .../Dialogs/ProcedureDialogEditorViewVM.cs | 59 +++- .../Tango.FSE.Procedures/IProcedureContext.cs | 22 +- .../Tango.FSE.Procedures/ProcedureContext.cs | 16 +- .../Tango.FSE.Procedures/ProceduresSettings.cs | 14 + .../Tango.FSE.Procedures.csproj | 8 + .../Tango.FSE.Procedures/Themes/Generic.xaml | 14 + .../ViewModels/ProcedureDesignerViewVM.cs | 63 +++- .../ViewModels/ResultsViewVM.cs | 6 +- .../Views/ProcedureDesignerView.xaml.cs | 10 + .../Tango.FSE.Procedures/WindowController.cs | 58 +++- .../Windows/ProcedureDialogWindow.xaml | 61 ++++ .../Windows/ProcedureDialogWindow.xaml.cs | 98 ++++++ .../FSE/Tango.FSE.Common/FSEViewModel.cs | 16 + .../Scripting/Tango.Scripting.Basic/Project.cs | 5 +- .../Tango.Scripting.Editors/Errors/ITextMarker.cs | 169 ++++++++++ .../Errors/TextMarkerService.cs | 365 +++++++++++++++++++++ .../Tango.Scripting.Editors/ScriptEditor.cs | 18 + .../Tango.Scripting.Editors.csproj | 2 + 20 files changed, 989 insertions(+), 48 deletions(-) create mode 100644 Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProceduresSettings.cs create mode 100644 Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml create mode 100644 Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs (limited to 'Software/Visual_Studio') diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Contracts/IProcedureDesignerView.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Contracts/IProcedureDesignerView.cs index 4dfb62cb7..5797d622a 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Contracts/IProcedureDesignerView.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Contracts/IProcedureDesignerView.cs @@ -19,5 +19,7 @@ namespace Tango.FSE.Procedures.Contracts void FocusCurrentEditor(); void ColorizeKeyword(String text); void ResetColrization(); + void HighlightError(int position, int length); + void ClearErrors(); } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/DialogController.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/DialogController.cs index 8d32f93c2..551b0d942 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/DialogController.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/DialogController.cs @@ -75,21 +75,28 @@ namespace Tango.FSE.Procedures public T FindControl(String name) where T : DependencyObject { - bool completed = false; - T child = null; - - DispatcherProvider.Invoke(() => - { - child = _rootElement.FindChild(name) as T; - completed = true; - }); - - while (!completed) + if (Thread.CurrentThread == Application.Current.Dispatcher.Thread) { - Thread.Sleep(10); + return _rootElement.FindChild(name) as T; } + else + { + bool completed = false; + T child = null; + + DispatcherProvider.Invoke(() => + { + child = _rootElement.FindChild(name) as T; + completed = true; + }); - return child; + while (!completed) + { + Thread.Sleep(10); + } + + return child; + } } } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Dialogs/ProcedureDialogEditorViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Dialogs/ProcedureDialogEditorViewVM.cs index 8c95df985..264f52127 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Dialogs/ProcedureDialogEditorViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Dialogs/ProcedureDialogEditorViewVM.cs @@ -11,7 +11,11 @@ using System.Windows.Markup; using System.Xml; using System.Xml.Linq; using Tango.Core.Commands; +using Tango.Core.DI; using Tango.FSE.Common; +using Tango.FSE.Common.Notifications; +using Tango.FSE.Common.Storage; +using Tango.Settings; namespace Tango.FSE.Procedures.Dialogs { @@ -19,6 +23,12 @@ namespace Tango.FSE.Procedures.Dialogs { private FileSystemWatcher _watcher; + [TangoInject] + private INotificationProvider NotificationProvider { get; set; } + + [TangoInject] + private IStorageProvider StorageProvider { get; set; } + private String _name; public String Name { @@ -79,26 +89,49 @@ namespace Tango.FSE.Procedures.Dialogs public ProcedureDialogEditorViewVM() { + TangoIOC.Default.Inject(this); OpenInBlendCommand = new RelayCommand(OpenInBlend); } - private void OpenInBlend() + private async void OpenInBlend() { - if (this._watcher != null) + if (_watcher != null) { return; } - String blendPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\Blend.exe"; + var settings = SettingsManager.Default.GetOrCreate(); - if (!File.Exists(blendPath)) + String blendPath = settings.BlendPath; + + if (String.IsNullOrWhiteSpace(blendPath) || !File.Exists(blendPath)) { - blendPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Blend.exe"; + blendPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\Blend.exe"; + + if (!File.Exists(blendPath)) + { + blendPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Blend.exe"; + } } if (!File.Exists(blendPath)) { - throw new FileNotFoundException("Could not locate Blend for Visual Studio location."); + if (await NotificationProvider.ShowWarningQuestion("Could not locate Blend for Visual Studio on your computer.\nDo you wish to manually browse for it on your hard drive?", "BROWSE", "CANCEL")) + { + var r = await StorageProvider.OpenFile("Browse for Blend for Visual Studio", "Executables|*.exe"); + if (!r.Confirmed) + { + return; + } + + settings.BlendPath = r.SelectedItem; + settings.Save(); + blendPath = settings.BlendPath; + } + else + { + return; + } } var tempFolder = TemporaryManager.CreateFolder(); @@ -154,24 +187,30 @@ namespace Tango.FSE.Procedures.Dialogs _watcher.EnableRaisingEvents = true; - Task.Factory.StartNew(() => + await Task.Factory.StartNew(() => { var p = Process.Start(blendPath, Path.Combine(tempFolder, "ProcedureDialog.csproj")); p.WaitForExit(); - _watcher.Dispose(); + ClearWatcher(); }); } protected override void Accept() { - _watcher?.Dispose(); + ClearWatcher(); base.Accept(); } protected override void Cancel() { - _watcher?.Dispose(); + ClearWatcher(); base.Cancel(); } + + private void ClearWatcher() + { + _watcher?.Dispose(); + _watcher = null; + } } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs index 4469ff9da..c304c05d0 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs @@ -91,7 +91,7 @@ namespace Tango.FSE.Procedures /// Sends a request by name with optional comma separated arguments. /// /// Name of the message. - /// The timeout. + /// The timeout in seconds. /// The arguments separated by commas. /// IMessage Send(String messageName, int? timeout = null, params Object[] args); @@ -109,7 +109,7 @@ namespace Tango.FSE.Procedures /// /// /// Name of the message. - /// The timeout. + /// The timeout in seconds. /// The arguments separated by commas. /// T Send(String messageName, int? timeout = null, params Object[] args) where T : class, IMessage; @@ -127,7 +127,7 @@ namespace Tango.FSE.Procedures /// Sends the specified message. /// /// The message. - /// The timeout. + /// The timeout in seconds. /// IMessage Send(IMessage message, int? timeout = null); @@ -136,7 +136,7 @@ namespace Tango.FSE.Procedures /// /// /// The message. - /// The timeout. + /// The timeout in seconds. /// T Send(IMessage message, int? timeout = null) where T : class, IMessage; @@ -146,7 +146,7 @@ namespace Tango.FSE.Procedures /// /// The message. /// Callback for continuous responses. - /// The timeout. + /// The timeout in seconds. void SendContinuous(IMessage message, Action callback, int? timeout) where T : class, IMessage; /// @@ -155,7 +155,7 @@ namespace Tango.FSE.Procedures /// /// Name of the message. /// Callback for continuous responses. - /// The timeout. + /// The timeout in seconds. /// The arguments. void SendContinuous(String messageName, Action callback, int? timeout, params Object[] args) where T : class, IMessage; @@ -363,12 +363,18 @@ namespace Tango.FSE.Procedures Thread RunAsync(Action action); /// - /// Loads the specified procedure dialog. + /// Loads the specified procedure dialog on the main UI thread. /// /// The name of the dialog file. /// IDialogController LoadDialog(String name); - IDialogController LoadDialogAsWindow(String name); + /// + /// Loads the specified procedure dialog as a window on the current thread. + /// + /// The name. + /// The window title + /// + IDialogController LoadDialogAsWindow(String name, String windowTitle); } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProcedureContext.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProcedureContext.cs index df314425c..f67749a92 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProcedureContext.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProcedureContext.cs @@ -564,7 +564,7 @@ namespace Tango.FSE.Procedures public IDialogController LoadDialog(string name) { - var dialog = _project.Dialogs.SingleOrDefault(x => x.Name == name); + var dialog = GetDialog(name); if (dialog == null) { throw new FileNotFoundException($"The specified dialog '{name}' could not be found in the project."); @@ -575,19 +575,25 @@ namespace Tango.FSE.Procedures return controller; } - public IDialogController LoadDialogAsWindow(string name) + public IDialogController LoadDialogAsWindow(string name, String windowTitle) { - var dialog = _project.Dialogs.SingleOrDefault(x => x.Name == name); + var dialog = GetDialog(name); if (dialog == null) { throw new FileNotFoundException($"The specified dialog '{name}' could not be found in the project."); } - WindowController controller = new WindowController(dialog.Xaml); + WindowController controller = new WindowController(dialog.Xaml, windowTitle); controller.Init(); return controller; } + private ProcedureDialog GetDialog(String name) + { + name = Path.GetFileNameWithoutExtension(name); + return _project.Dialogs.SingleOrDefault(x => x.Name.ToLower() == name.ToLower() + ".xaml"); + } + private void AutoInvoke(Task task) { bool completed = false; @@ -599,7 +605,7 @@ namespace Tango.FSE.Procedures completed = true; }); - DispatcherProvider.Invoke((Action)(async () => + DispatcherProvider.Invoke((Action)(async () => { await task; })); diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProceduresSettings.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProceduresSettings.cs new file mode 100644 index 000000000..0c885081f --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProceduresSettings.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Settings; + +namespace Tango.FSE.Procedures +{ + public class ProceduresSettings : SettingsBase + { + public String BlendPath { get; set; } + } +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj index 446ca7bde..f98771187 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj @@ -143,6 +143,7 @@ + @@ -177,6 +178,9 @@ ResultsView.xaml + + ProcedureDialogWindow.xaml + @@ -339,6 +343,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml index 0aa4727c5..06e60997d 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml @@ -453,4 +453,18 @@ + + \ No newline at end of file diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ProcedureDesignerViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ProcedureDesignerViewVM.cs index 30e10fca5..859570b64 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ProcedureDesignerViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ProcedureDesignerViewVM.cs @@ -19,6 +19,7 @@ using Tango.Core.Commands; using Tango.Core.ExtensionMethods; using Tango.FSE.Common; using Tango.FSE.Common.Navigation; +using Tango.FSE.Common.Notifications; using Tango.FSE.Procedures.Contracts; using Tango.FSE.Procedures.Dialogs; using Tango.FSE.Procedures.Messages; @@ -293,6 +294,7 @@ namespace Tango.FSE.Procedures.ViewModels DecreaseFontSizeCommand = new RelayCommand(() => FontSize--); AddDialogCommand = new RelayCommand(AddProcedureDialog); EditDialogCommand = new RelayCommand(EditProcedureDialog); + RemoveDialogCommand = new RelayCommand(RemoveProcedureDialog); } #endregion @@ -449,11 +451,22 @@ namespace Tango.FSE.Procedures.ViewModels var result = await ProjectRunner.Compile(); CompilationErrors = result.Errors; + View.ClearErrors(); + if (CompilationErrors.Count > 0) { Logger.WriteLine("Project compiled with errors."); Status = "Project compiled with errors."; SelectedToolWindow = ToolWindows.Errors; + + foreach (var error in CompilationErrors) + { + if (error.File == SelectedScript.Name) + { + View.HighlightError(error.Position, error.Length); + } + } + return false; } else @@ -563,6 +576,19 @@ namespace Tango.FSE.Procedures.ViewModels { SelectedToolWindow = ToolWindows.Errors; } + + InvokeUI(() => + { + View.ClearErrors(); + + foreach (var error in CompilationErrors) + { + if (error.File == SelectedScript.Name) + { + View.HighlightError(error.Position, error.Length); + } + } + }); } _compileTimer.Start(); @@ -644,6 +670,8 @@ namespace Tango.FSE.Procedures.ViewModels Status = "Project saved."; + NotificationProvider.PushSnackbarItem(MessageType.Success, "Procedure project saved", false, $"Project '{Project.Name}' saved successfully.", TimeSpan.FromSeconds(1.5)); + Project.Scripts.ToList().ForEach(x => x.IsChanged = false); _isProjectChanged = false; } @@ -808,6 +836,12 @@ namespace Tango.FSE.Procedures.ViewModels if (result.Confirmed) { + if (Project.Scripts.Any(x => x != script && x.Name.ToLower() == result.Input.ToLower() + ".csx")) + { + await NotificationProvider.ShowError($"The project already contains a file named '{result.Input}'."); + return; + } + script.Name = result.Input + ".csx"; script.IsChanged = true; } @@ -1060,13 +1094,19 @@ namespace Tango.FSE.Procedures.ViewModels private async void AddProcedureDialog() { - var result = await NotificationProvider.ShowInputBox("New Procedure Dialog", "Please specify the dialog name", PackIconKind.WindowRestore, "MyDialog.xaml", "Dialog Name", 30); + var result = await NotificationProvider.ShowInputBox("New Procedure Dialog", "Please specify the dialog name", PackIconKind.WindowRestore, $"Dialog{Project.Dialogs.Count + 1}", "Dialog Name", 30); if (result.Confirmed) { + if (Project.Dialogs.Any(x => x.Name.ToLower() == result.Input.ToLower() + ".xaml")) + { + await NotificationProvider.ShowError($"The project already contains a dialog named '{result.Input}'."); + return; + } + Project.Dialogs.Add(new ProcedureDialog() { - Name = result.Input, + Name = result.Input + ".xaml", Xaml = Properties.Resources.dialog_template }); } @@ -1076,17 +1116,32 @@ namespace Tango.FSE.Procedures.ViewModels { var vm = await NotificationProvider.ShowDialog(new ProcedureDialogEditorViewVM() { - Name = dialog.Name, + Name = Path.GetFileNameWithoutExtension(dialog.Name), Xaml = dialog.Xaml, }); if (vm.DialogResult) { - dialog.Name = vm.Name; + if (Project.Dialogs.Any(x => x != dialog && x.Name.ToLower() == vm.Name.ToLower() + ".xaml")) + { + await NotificationProvider.ShowError($"The project already contains a dialog named '{vm.Name}'."); + return; + } + + dialog.Name = vm.Name + ".xaml"; dialog.Xaml = vm.Xaml; } } + private async void RemoveProcedureDialog(ProcedureDialog dialog) + { + if (await NotificationProvider.ShowWarningQuestion($"Are you sure you want to delete '{dialog.Name}'?")) + { + Project.Dialogs.Remove(dialog); + _isProjectChanged = true; + } + } + #endregion } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ResultsViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ResultsViewVM.cs index 2fa496fb7..dfad0017d 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ResultsViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ResultsViewVM.cs @@ -10,6 +10,7 @@ using Tango.Core.Commands; using Tango.Core.DI; using Tango.CSV; using Tango.FSE.Common; +using Tango.FSE.Common.Notifications; using Tango.FSE.Procedures.CSV; using Tango.FSE.Procedures.Dialogs; @@ -126,7 +127,10 @@ namespace Tango.FSE.Procedures.ViewModels } }); - await NotificationProvider.ShowSuccess("File exported successfully."); + NotificationProvider.PushSnackbarItem(MessageType.Success, "CSV file exported", true, "Procedure result was exported to file.\nTap to browse the file location.", TimeSpan.FromSeconds(5), null, () => + { + StorageProvider.ShowInExplorer(r.SelectedItem); + }); } catch (Exception ex) { diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs index 8033656c4..ee21509b2 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs @@ -127,5 +127,15 @@ namespace Tango.FSE.Procedures.Views { _vm.EditProcedureDialog((sender as FrameworkElement).DataContext as ProcedureDialog); } + + public void HighlightError(int position, int length) + { + GetCurrentEditor()?.HighlighError(position, length); + } + + public void ClearErrors() + { + GetAllEditors().ForEach(x => x.ClearErrors()); + } } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/WindowController.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/WindowController.cs index f70ce3cfd..f0efc9b83 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/WindowController.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/WindowController.cs @@ -10,9 +10,12 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Interop; using System.Windows.Markup; +using System.Xml.Linq; using Tango.Core.DI; using Tango.FSE.Common.Notifications; using Tango.FSE.Common.Threading; +using Tango.FSE.Common.WindowsManager; +using Tango.FSE.Procedures.Windows; namespace Tango.FSE.Procedures { @@ -24,7 +27,9 @@ namespace Tango.FSE.Procedures private String _xaml; private FrameworkElement _rootElement; - private Window _dialogWindow; + private ProcedureDialogWindow _dialogWindow; + private Thread _windowThread; + private String _title; [TangoInject] private IDispatcherProvider DispatcherProvider { get; set; } @@ -37,30 +42,50 @@ namespace Tango.FSE.Procedures } } - internal WindowController(String xaml) + internal WindowController(String xaml, String title) { _xaml = xaml; + _title = title; TangoIOC.Default.Inject(this); } internal void Init() { + String xaml = _xaml; + + XDocument doc = XDocument.Parse(xaml); + var textBoxes = doc.Descendants().Where(x => x.Name.LocalName == "TextBox").ToList(); + textBoxes.ForEach(x => x.SetAttributeValue("Style", "{StaticResource ProcedureWindowTextBoxStyle}")); + xaml = doc.ToString(); + FrameworkElement rootElement; var stream = new MemoryStream(); var writer = new StreamWriter(stream); - writer.Write(_xaml); + writer.Write(xaml); writer.Flush(); stream.Position = 0; rootElement = (FrameworkElement)XamlReader.Load(stream); _rootElement = rootElement; stream.Dispose(); - _dialogWindow = new Window(); - _dialogWindow.Content = rootElement; + _dialogWindow = new ProcedureDialogWindow(); + _dialogWindow.Width = _rootElement.Width; + _dialogWindow.Height = _rootElement.Height; + _dialogWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen; + _dialogWindow.Title = _title; + _dialogWindow.SetContent(rootElement); + + //var textBoxesToRestore = rootElement.FindVisualChildren().ToList(); + //textBoxesToRestore.ForEach(x => x.Style = null); + var helper = new WindowInteropHelper(_dialogWindow); helper.EnsureHandle(); + _windowThread = _dialogWindow.Dispatcher.Thread; + + //System.Windows.Threading.Dispatcher.Run(); + //Make the main window as owner ? could be risky.. //IntPtr mainWindowHanle = IntPtr.Zero; @@ -80,7 +105,28 @@ namespace Tango.FSE.Procedures public T FindControl(String name) where T : DependencyObject { - return _rootElement.FindChild(name) as T; + if (Thread.CurrentThread == _windowThread) + { + return _rootElement.FindChild(name) as T; + } + else + { + bool completed = false; + T child = null; + + DispatcherProvider.Invoke(() => + { + child = _rootElement.FindChild(name) as T; + completed = true; + }); + + while (!completed) + { + Thread.Sleep(10); + } + + return child; + } } public void Close() diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml new file mode 100644 index 000000000..58e29d055 --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml.cs new file mode 100644 index 000000000..e3abad343 --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml.cs @@ -0,0 +1,98 @@ +using MahApps.Metro.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace Tango.FSE.Procedures.Windows +{ + /// + /// Interaction logic for ChildWindow.xaml + /// + public partial class ProcedureDialogWindow : MetroWindow + { + private Point _startPoint; + private bool _isMouseDown; + + public ProcedureDialogWindow() + { + InitializeComponent(); + + btnMinimize.Click += (_, __) => WindowState = WindowState.Minimized; + btnMaximize.Click += (_, __) => WindowState = WindowState == WindowState.Maximized ? WindowState = WindowState.Normal : WindowState = WindowState.Maximized; + btnClose.Click += (_, __) => Close(); + gridTitle.MouseLeftButtonDown += (x, e) => + { + Mouse.Capture(gridTitle); + + _startPoint = e.GetPosition(this); + + if (e.ClickCount > 1 && btnMaximize.IsEnabled) + { + WindowState = WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal; + return; + } + + _isMouseDown = true; + }; + gridTitle.MouseLeftButtonUp += (x, e) => + { + _isMouseDown = false; + gridTitle.ReleaseMouseCapture(); + }; + + gridTitle.MouseMove += GridTitle_MouseMove; + StateChanged += MainWindow_StateChanged; + } + + private void MainWindow_StateChanged(object sender, EventArgs e) + { + if (WindowState == WindowState.Maximized) + { + btnMaximize.Icon = MaterialDesignThemes.Wpf.PackIconKind.WindowRestore; + } + else if (WindowState == WindowState.Normal) + { + btnMaximize.Icon = MaterialDesignThemes.Wpf.PackIconKind.WindowMaximize; + } + } + + private void GridTitle_MouseMove(object sender, MouseEventArgs e) + { + if (_isMouseDown) + { + if (WindowState == WindowState.Maximized) + { + var previousWidth = Width; + WindowState = WindowState.Normal; + var currentWidth = Width; + var locationPrecentageBefore = _startPoint.X / previousWidth; + var newLocationX = currentWidth * locationPrecentageBefore; + _startPoint = new Point(newLocationX, _startPoint.Y); + } + else + { + Point pointToWindow = Mouse.GetPosition(this); + Point pointToScreen = PointToScreen(pointToWindow); + + Left = pointToScreen.X - _startPoint.X; + Top = pointToScreen.Y - _startPoint.Y; + } + } + } + + public void SetContent(FrameworkElement element) + { + contentBorder.Child = element; + } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs index 7dcfcb3c8..d6e3a5b52 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs @@ -528,4 +528,20 @@ namespace Tango.FSE.Common } } + + public abstract class FSEViewModelWithModuleSettings : FSEViewModel where TView : IFSEView where TSettings : SettingsBase + { + /// + /// Gets or sets the module settings. + /// + public TSettings ModuleSettings { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public FSEViewModelWithModuleSettings() + { + ModuleSettings = SettingsManager.Default.GetOrCreate(); + } + } } \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Basic/Project.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Basic/Project.cs index 13ebda6bb..8ca64ca18 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Basic/Project.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Basic/Project.cs @@ -85,11 +85,12 @@ namespace Tango.Scripting.Basic foreach (var file in Scripts.Where(x => !x.IsEntryPoint && script != x).Select(x => Path.Combine(tempFolder, x.Name))) { - loadingString = $"#load \"{file}\"\n"; + loadingString += $"#load \"{file}\"\n"; script.LoadCount++; - script.LoadCharCount += loadingString.Length; } + script.LoadCharCount += loadingString.Length; + code = loadingString + code; if (!script.IsEntryPoint) diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs new file mode 100644 index 000000000..dcbf8388a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs @@ -0,0 +1,169 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; + +namespace Tango.Scripting.Editors +{ + /// + /// Represents a text marker. + /// + public interface ITextMarker + { + /// + /// Gets the start offset of the marked text region. + /// + int StartOffset { get; } + + /// + /// Gets the end offset of the marked text region. + /// + int EndOffset { get; } + + /// + /// Gets the length of the marked region. + /// + int Length { get; } + + /// + /// Deletes the text marker. + /// + void Delete(); + + /// + /// Gets whether the text marker was deleted. + /// + bool IsDeleted { get; } + + /// + /// Event that occurs when the text marker is deleted. + /// + event EventHandler Deleted; + + /// + /// Gets/Sets the background color. + /// + Color? BackgroundColor { get; set; } + + /// + /// Gets/Sets the foreground color. + /// + Color? ForegroundColor { get; set; } + + /// + /// Gets/Sets the font weight. + /// + FontWeight? FontWeight { get; set; } + + /// + /// Gets/Sets the font style. + /// + FontStyle? FontStyle { get; set; } + + /// + /// Gets/Sets the type of the marker. Use TextMarkerType.None for normal markers. + /// + TextMarkerTypes MarkerTypes { get; set; } + + /// + /// Gets/Sets the color of the marker. + /// + Color MarkerColor { get; set; } + + /// + /// Gets/Sets an object with additional data for this text marker. + /// + object Tag { get; set; } + + /// + /// Gets/Sets an object that will be displayed as tooltip in the text editor. + /// + /// Not supported in this sample! + object ToolTip { get; set; } + } + + [Flags] + public enum TextMarkerTypes + { + /// + /// Use no marker + /// + None = 0x0000, + /// + /// Use squiggly underline marker + /// + SquigglyUnderline = 0x001, + /// + /// Normal underline. + /// + NormalUnderline = 0x002, + /// + /// Dotted underline. + /// + DottedUnderline = 0x004, + + /// + /// Horizontal line in the scroll bar. + /// + LineInScrollBar = 0x0100, + /// + /// Small triangle in the scroll bar, pointing to the right. + /// + ScrollBarRightTriangle = 0x0400, + /// + /// Small triangle in the scroll bar, pointing to the left. + /// + ScrollBarLeftTriangle = 0x0800, + /// + /// Small circle in the scroll bar. + /// + CircleInScrollBar = 0x1000 + } + + public interface ITextMarkerService + { + /// + /// Creates a new text marker. The text marker will be invisible at first, + /// you need to set one of the Color properties to make it visible. + /// + ITextMarker Create(int startOffset, int length); + + /// + /// Gets the list of text markers. + /// + IEnumerable TextMarkers { get; } + + /// + /// Removes the specified text marker. + /// + void Remove(ITextMarker marker); + + /// + /// Removes all text markers that match the condition. + /// + void RemoveAll(Predicate predicate); + + /// + /// Finds all text markers at the specified offset. + /// + IEnumerable GetMarkersAtOffset(int offset); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs new file mode 100644 index 000000000..2bb3d8e03 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs @@ -0,0 +1,365 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors +{ + /// + /// Handles the text markers for a code editor. + /// + public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService, ITextViewConnect + { + TextSegmentCollection markers; + TextDocument document; + + public TextMarkerService(TextDocument document) + { + if (document == null) + throw new ArgumentNullException("document"); + this.document = document; + this.markers = new TextSegmentCollection(document); + } + + #region ITextMarkerService + public ITextMarker Create(int startOffset, int length) + { + if (markers == null) + throw new InvalidOperationException("Cannot create a marker when not attached to a document"); + + int textLength = document.TextLength; + if (startOffset < 0 || startOffset > textLength) + throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between 0 and " + textLength); + if (length < 0 || startOffset + length > textLength) + throw new ArgumentOutOfRangeException("length", length, "length must not be negative and startOffset+length must not be after the end of the document"); + + TextMarker m = new TextMarker(this, startOffset, length); + markers.Add(m); + // no need to mark segment for redraw: the text marker is invisible until a property is set + return m; + } + + public IEnumerable GetMarkersAtOffset(int offset) + { + if (markers == null) + return Enumerable.Empty(); + else + return markers.FindSegmentsContaining(offset); + } + + public IEnumerable TextMarkers { + get { return markers ?? Enumerable.Empty(); } + } + + public void RemoveAll(Predicate predicate) + { + if (predicate == null) + throw new ArgumentNullException("predicate"); + if (markers != null) { + foreach (TextMarker m in markers.ToArray()) { + if (predicate(m)) + Remove(m); + } + } + } + + public void Remove(ITextMarker marker) + { + if (marker == null) + throw new ArgumentNullException("marker"); + TextMarker m = marker as TextMarker; + if (markers != null && markers.Remove(m)) { + Redraw(m); + m.OnDeleted(); + } + } + + /// + /// Redraws the specified text segment. + /// + internal void Redraw(ISegment segment) + { + foreach (var view in textViews) { + view.Redraw(segment, DispatcherPriority.Normal); + } + if (RedrawRequested != null) + RedrawRequested(this, EventArgs.Empty); + } + + public event EventHandler RedrawRequested; + #endregion + + #region DocumentColorizingTransformer + protected override void ColorizeLine(DocumentLine line) + { + if (markers == null) + return; + int lineStart = line.Offset; + int lineEnd = lineStart + line.Length; + foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length)) { + Brush foregroundBrush = null; + if (marker.ForegroundColor != null) { + foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value); + foregroundBrush.Freeze(); + } + ChangeLinePart( + Math.Max(marker.StartOffset, lineStart), + Math.Min(marker.EndOffset, lineEnd), + element => { + if (foregroundBrush != null) { + element.TextRunProperties.SetForegroundBrush(foregroundBrush); + } + Typeface tf = element.TextRunProperties.Typeface; + element.TextRunProperties.SetTypeface(new Typeface( + tf.FontFamily, + marker.FontStyle ?? tf.Style, + marker.FontWeight ?? tf.Weight, + tf.Stretch + )); + } + ); + } + } + #endregion + + #region IBackgroundRenderer + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + if (markers == null || !textView.VisualLinesValid) + return; + var visualLines = textView.VisualLines; + if (visualLines.Count == 0) + return; + int viewStart = visualLines.First().FirstDocumentLine.Offset; + int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; + foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + if (marker.BackgroundColor != null) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToWholePixels = true; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, marker); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + Color color = marker.BackgroundColor.Value; + SolidColorBrush brush = new SolidColorBrush(color); + brush.Freeze(); + drawingContext.DrawGeometry(brush, null, geometry); + } + } + var underlineMarkerTypes = TextMarkerTypes.SquigglyUnderline | TextMarkerTypes.NormalUnderline | TextMarkerTypes.DottedUnderline; + if ((marker.MarkerTypes & underlineMarkerTypes) != 0) { + foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) { + Point startPoint = r.BottomLeft; + Point endPoint = r.BottomRight; + + Brush usedBrush = new SolidColorBrush(marker.MarkerColor); + usedBrush.Freeze(); + if ((marker.MarkerTypes & TextMarkerTypes.SquigglyUnderline) != 0) { + double offset = 2.5; + + int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4); + + StreamGeometry geometry = new StreamGeometry(); + + using (StreamGeometryContext ctx = geometry.Open()) { + ctx.BeginFigure(startPoint, false, false); + ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false); + } + + geometry.Freeze(); + + Pen usedPen = new Pen(usedBrush, 1); + usedPen.Freeze(); + drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry); + } + if ((marker.MarkerTypes & TextMarkerTypes.NormalUnderline) != 0) { + Pen usedPen = new Pen(usedBrush, 1); + usedPen.Freeze(); + drawingContext.DrawLine(usedPen, startPoint, endPoint); + } + if ((marker.MarkerTypes & TextMarkerTypes.DottedUnderline) != 0) { + Pen usedPen = new Pen(usedBrush, 1); + usedPen.DashStyle = DashStyles.Dot; + usedPen.Freeze(); + drawingContext.DrawLine(usedPen, startPoint, endPoint); + } + } + } + } + } + + IEnumerable CreatePoints(Point start, Point end, double offset, int count) + { + for (int i = 0; i < count; i++) + yield return new Point(start.X + i * offset, start.Y - ((i + 1) % 2 == 0 ? offset : 0)); + } + #endregion + + #region ITextViewConnect + readonly List textViews = new List(); + + void ITextViewConnect.AddToTextView(TextView textView) + { + if (textView != null && !textViews.Contains(textView)) { + Debug.Assert(textView.Document == document); + textViews.Add(textView); + } + } + + void ITextViewConnect.RemoveFromTextView(TextView textView) + { + if (textView != null) { + Debug.Assert(textView.Document == document); + textViews.Remove(textView); + } + } + #endregion + } + + public sealed class TextMarker : TextSegment, ITextMarker + { + readonly TextMarkerService service; + + public TextMarker(TextMarkerService service, int startOffset, int length) + { + if (service == null) + throw new ArgumentNullException("service"); + this.service = service; + this.StartOffset = startOffset; + this.Length = length; + this.markerTypes = TextMarkerTypes.None; + } + + public event EventHandler Deleted; + + public bool IsDeleted { + get { return !this.IsConnectedToCollection; } + } + + public void Delete() + { + service.Remove(this); + } + + internal void OnDeleted() + { + if (Deleted != null) + Deleted(this, EventArgs.Empty); + } + + void Redraw() + { + service.Redraw(this); + } + + Color? backgroundColor; + + public Color? BackgroundColor { + get { return backgroundColor; } + set { + if (backgroundColor != value) { + backgroundColor = value; + Redraw(); + } + } + } + + Color? foregroundColor; + + public Color? ForegroundColor { + get { return foregroundColor; } + set { + if (foregroundColor != value) { + foregroundColor = value; + Redraw(); + } + } + } + + FontWeight? fontWeight; + + public FontWeight? FontWeight { + get { return fontWeight; } + set { + if (fontWeight != value) { + fontWeight = value; + Redraw(); + } + } + } + + FontStyle? fontStyle; + + public FontStyle? FontStyle { + get { return fontStyle; } + set { + if (fontStyle != value) { + fontStyle = value; + Redraw(); + } + } + } + + public object Tag { get; set; } + + TextMarkerTypes markerTypes; + + public TextMarkerTypes MarkerTypes { + get { return markerTypes; } + set { + if (markerTypes != value) { + markerTypes = value; + Redraw(); + } + } + } + + Color markerColor; + + public Color MarkerColor { + get { return markerColor; } + set { + if (markerColor != value) { + markerColor = value; + Redraw(); + } + } + } + + public object ToolTip { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs index 7b5c38a2b..8cf615a7c 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel.Design; using System.Diagnostics; using System.IO; using System.Linq; @@ -56,6 +57,7 @@ namespace Tango.Scripting.Editors private List _knownTypes; private List _declaredTypes; private bool _isLoadingTypes; + private TextMarkerService errorMarkerService; private static JsonSerializerSettings _jsonSettings; private static Dictionary _knownTypesCache; private static String KNOWN_TYPES_CACHE_FOLDER; @@ -296,6 +298,10 @@ namespace Tango.Scripting.Editors completionWindow.InsertionRequest += CompletionWindow_InsertionRequest; TextChanged += ScriptEditor_TextChanged; + + errorMarkerService = new TextMarkerService(Document); + TextArea.TextView.BackgroundRenderers.Add(errorMarkerService); + TextArea.TextView.LineTransformers.Add(errorMarkerService); } private void ScriptEditor_KnownTypesAvailable(object sender, EventArgs e) @@ -2423,6 +2429,18 @@ namespace Tango.Scripting.Editors Document.EndUpdate(); } + public void HighlighError(int position, int length) + { + ITextMarker marker = errorMarkerService.Create(position, length); + marker.MarkerTypes = TextMarkerTypes.SquigglyUnderline; + marker.MarkerColor = Colors.Red; + } + + public void ClearErrors() + { + errorMarkerService.RemoveAll(m => true); + } + #endregion } } diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj index 798529616..a70bbf3de 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj @@ -196,6 +196,8 @@ + + -- cgit v1.3.1