aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2020-07-27 15:25:20 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2020-07-27 15:25:20 +0300
commit925bf377913f1003324d746b9e28e053186bfa29 (patch)
tree34239e4e10738770c0d7375c7e5a28c2f1812415
parent2418e272c79e617b84e589c14818ea3aa6b1a84a (diff)
downloadTango-925bf377913f1003324d746b9e28e053186bfa29.tar.gz
Tango-925bf377913f1003324d746b9e28e053186bfa29.zip
Procedures:
Fixed issue with TextBox on DialogWindow. Fixed issue with multiple library linking. Implemented error checking. Many improvements.
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Contracts/IProcedureDesignerView.cs2
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/DialogController.cs31
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Dialogs/ProcedureDialogEditorViewVM.cs59
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs22
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProcedureContext.cs16
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ProceduresSettings.cs14
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj8
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml14
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ProcedureDesignerViewVM.cs63
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ViewModels/ResultsViewVM.cs6
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs10
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/WindowController.cs58
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml61
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Windows/ProcedureDialogWindow.xaml.cs98
-rw-r--r--Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs16
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Basic/Project.cs5
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs169
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs365
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs18
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj2
20 files changed, 989 insertions, 48 deletions
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<T>(String name) where T : DependencyObject
{
- bool completed = false;
- T child = null;
-
- DispatcherProvider.Invoke(() =>
- {
- child = _rootElement.FindChild<T>(name) as T;
- completed = true;
- });
-
- while (!completed)
+ if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
{
- Thread.Sleep(10);
+ return _rootElement.FindChild<T>(name) as T;
}
+ else
+ {
+ bool completed = false;
+ T child = null;
+
+ DispatcherProvider.Invoke(() =>
+ {
+ child = _rootElement.FindChild<T>(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<ProceduresSettings>();
- 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.
/// </summary>
/// <param name="messageName">Name of the message.</param>
- /// <param name="timeout">The timeout.</param>
+ /// <param name="timeout">The timeout in seconds.</param>
/// <param name="args">The arguments separated by commas.</param>
/// <returns></returns>
IMessage Send(String messageName, int? timeout = null, params Object[] args);
@@ -109,7 +109,7 @@ namespace Tango.FSE.Procedures
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="messageName">Name of the message.</param>
- /// <param name="timeout">The timeout.</param>
+ /// <param name="timeout">The timeout in seconds.</param>
/// <param name="args">The arguments separated by commas.</param>
/// <returns></returns>
T Send<T>(String messageName, int? timeout = null, params Object[] args) where T : class, IMessage;
@@ -127,7 +127,7 @@ namespace Tango.FSE.Procedures
/// Sends the specified message.
/// </summary>
/// <param name="message">The message.</param>
- /// <param name="timeout">The timeout.</param>
+ /// <param name="timeout">The timeout in seconds.</param>
/// <returns></returns>
IMessage Send(IMessage message, int? timeout = null);
@@ -136,7 +136,7 @@ namespace Tango.FSE.Procedures
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message">The message.</param>
- /// <param name="timeout">The timeout.</param>
+ /// <param name="timeout">The timeout in seconds.</param>
/// <returns></returns>
T Send<T>(IMessage message, int? timeout = null) where T : class, IMessage;
@@ -146,7 +146,7 @@ namespace Tango.FSE.Procedures
/// <typeparam name="T"></typeparam>
/// <param name="message">The message.</param>
/// <param name="callback">Callback for continuous responses.</param>
- /// <param name="timeout">The timeout.</param>
+ /// <param name="timeout">The timeout in seconds.</param>
void SendContinuous<T>(IMessage message, Action<T> callback, int? timeout) where T : class, IMessage;
/// <summary>
@@ -155,7 +155,7 @@ namespace Tango.FSE.Procedures
/// <typeparam name="T"></typeparam>
/// <param name="messageName">Name of the message.</param>
/// <param name="callback">Callback for continuous responses.</param>
- /// <param name="timeout">The timeout.</param>
+ /// <param name="timeout">The timeout in seconds.</param>
/// <param name="args">The arguments.</param>
void SendContinuous<T>(String messageName, Action<T> callback, int? timeout, params Object[] args) where T : class, IMessage;
@@ -363,12 +363,18 @@ namespace Tango.FSE.Procedures
Thread RunAsync(Action action);
/// <summary>
- /// Loads the specified procedure dialog.
+ /// Loads the specified procedure dialog on the main UI thread.
/// </summary>
/// <param name="name">The name of the dialog file.</param>
/// <returns></returns>
IDialogController LoadDialog(String name);
- IDialogController LoadDialogAsWindow(String name);
+ /// <summary>
+ /// Loads the specified procedure dialog as a window on the current thread.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="windowTitle">The window title</param>
+ /// <returns></returns>
+ 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 @@
<Compile Include="IProcedureLogger.cs" />
<Compile Include="Messages\ProcedureProjectPublishedOrSuppressed.cs" />
<Compile Include="ProcedureDialog.cs" />
+ <Compile Include="ProceduresSettings.cs" />
<Compile Include="ProjectRunner.cs" />
<Compile Include="ProjectRunnerState.cs" />
<Compile Include="Result.cs" />
@@ -177,6 +178,9 @@
<DependentUpon>ResultsView.xaml</DependentUpon>
</Compile>
<Compile Include="WindowController.cs" />
+ <Compile Include="Windows\ProcedureDialogWindow.xaml.cs">
+ <DependentUpon>ProcedureDialogWindow.xaml</DependentUpon>
+ </Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
@@ -339,6 +343,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
+ <Page Include="Windows\ProcedureDialogWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
</ItemGroup>
<ItemGroup>
<Resource Include="Images\test_designer.png" />
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 @@
</Setter>
</Style>
+ <Style x:Key="ProcedureWindowTextBoxStyle" TargetType="TextBox">
+ <Setter Property="Foreground" Value="{StaticResource FSE_PrimaryForegroundBrush}"></Setter>
+ <Setter Property="CaretBrush" Value="{StaticResource FSE_PrimaryForegroundBrush}"></Setter>
+ <Setter Property="Background" Value="Transparent"></Setter>
+ <Setter Property="BorderBrush" Value="{StaticResource FSE_BorderBrush}"></Setter>
+ <Setter Property="BorderThickness" Value="0 0 0 1"></Setter>
+ <Style.Triggers>
+ <Trigger Property="IsFocused" Value="True">
+ <Setter Property="BorderBrush" Value="{StaticResource FSE_PrimaryAccentBrush}"></Setter>
+ <Setter Property="BorderThickness" Value="0 0 0 2"></Setter>
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+
</ResourceDictionary> \ 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<ProcedureDialog>(EditProcedureDialog);
+ RemoveDialogCommand = new RelayCommand<ProcedureDialog>(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<TextBox>().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<T>(String name) where T : DependencyObject
{
- return _rootElement.FindChild<T>(name) as T;
+ if (Thread.CurrentThread == _windowThread)
+ {
+ return _rootElement.FindChild<T>(name) as T;
+ }
+ else
+ {
+ bool completed = false;
+ T child = null;
+
+ DispatcherProvider.Invoke(() =>
+ {
+ child = _rootElement.FindChild<T>(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 @@
+<mahapps:MetroWindow x:Class="Tango.FSE.Procedures.Windows.ProcedureDialogWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:mahapps="http://metro.mahapps.com/winfx/xaml/controls"
+ xmlns:commonControls="clr-namespace:Tango.FSE.Common.Controls;assembly=Tango.FSE.Common"
+ xmlns:local="clr-namespace:Tango.FSE.Procedures.Windows"
+ mc:Ignorable="d"
+ Title="Tango FSE"
+ TextElement.Foreground="{StaticResource FSE_PrimaryForegroundBrush}"
+ Background="{StaticResource FSE_PrimaryBackgroundBrush}"
+ TextElement.FontSize="{StaticResource FSE_DefaultFontSize}"
+ TitleCharacterCasing="Normal"
+ UseNoneWindowStyle="True"
+ EnableDWMDropShadow="True"
+ BorderThickness="1"
+ BorderBrush="Gray"
+ FontFamily="{StaticResource flexo}"
+ WindowTitleBrush="{StaticResource FSE_PrimaryBackgroundBrush}"
+ TitleForeground="{StaticResource FSE_PrimaryForegroundBrush}"
+ WindowTransitionsEnabled="False" d:DesignWidth="1000" d:DesignHeight="600">
+
+ <Grid>
+ <DockPanel>
+ <Grid Height="40" DockPanel.Dock="Top" Background="{StaticResource FSE_PrimaryBackgroundLightBrush}">
+
+ <StackPanel Margin="5 0 0 0" HorizontalAlignment="Left" Orientation="Horizontal">
+ <Image Source="{StaticResource FSE_Machine_Full}" RenderOptions.BitmapScalingMode="Fant" Margin="8" />
+ <TextBlock FontWeight="SemiBold" Margin="5 0 0 0" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=Title,FallbackValue='Tango FSE'}"></TextBlock>
+ </StackPanel>
+
+ <Image Source="{StaticResource FSE_Twine_Logo_Colored}" RenderOptions.BitmapScalingMode="Fant" HorizontalAlignment="Center" Margin="5"></Image>
+
+ <Polygon HorizontalAlignment="Right" Points="200,0 300,0 300,100 0,100" Width="250" Stretch="Fill">
+ <Polygon.Fill>
+ <ImageBrush Viewport="0 0 800 300" ViewportUnits="Absolute" ImageSource="{StaticResource FSE_Abstract}" Stretch="None" Opacity="0.6" />
+ </Polygon.Fill>
+ </Polygon>
+ </Grid>
+ <Grid Margin="0,-2,0,2">
+ <Border x:Name="contentBorder" x:FieldModifier="public" BorderBrush="{StaticResource FSE_PrimaryBackgroundBrush}" BorderThickness="2">
+
+ </Border>
+
+ <Image IsHitTestVisible="False" Source="{StaticResource FSE_Shadow_Top}" VerticalAlignment="Top" Stretch="Fill" Height="10" Opacity="0.6" />
+ </Grid>
+ </DockPanel>
+
+ <Grid VerticalAlignment="Top" Height="40">
+ <DockPanel>
+ <UniformGrid DockPanel.Dock="Right" Columns="3" HorizontalAlignment="Right" Width="130">
+ <commonControls:IconButton Padding="8" x:Name="btnMinimize" Icon="WindowMinimize" />
+ <commonControls:IconButton Padding="8" x:Name="btnMaximize" Icon="WindowMaximize" />
+ <commonControls:IconButton Padding="8" x:Name="btnClose" Icon="WindowClose" ToolTip="Close and attach to main application" />
+ </UniformGrid>
+ <Grid Background="Transparent" x:Name="gridTitle" Margin="0 0 0 0" Height="50" VerticalAlignment="Top"></Grid>
+ </DockPanel>
+ </Grid>
+ </Grid>
+</mahapps:MetroWindow>
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
+{
+ /// <summary>
+ /// Interaction logic for ChildWindow.xaml
+ /// </summary>
+ 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<TView,TSettings> : FSEViewModel<TView> where TView : IFSEView where TSettings : SettingsBase
+ {
+ /// <summary>
+ /// Gets or sets the module settings.
+ /// </summary>
+ public TSettings ModuleSettings { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FSEViewModelWithModuleSettings{T}"/> class.
+ /// </summary>
+ public FSEViewModelWithModuleSettings()
+ {
+ ModuleSettings = SettingsManager.Default.GetOrCreate<TSettings>();
+ }
+ }
} \ 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
+{
+ /// <summary>
+ /// Represents a text marker.
+ /// </summary>
+ public interface ITextMarker
+ {
+ /// <summary>
+ /// Gets the start offset of the marked text region.
+ /// </summary>
+ int StartOffset { get; }
+
+ /// <summary>
+ /// Gets the end offset of the marked text region.
+ /// </summary>
+ int EndOffset { get; }
+
+ /// <summary>
+ /// Gets the length of the marked region.
+ /// </summary>
+ int Length { get; }
+
+ /// <summary>
+ /// Deletes the text marker.
+ /// </summary>
+ void Delete();
+
+ /// <summary>
+ /// Gets whether the text marker was deleted.
+ /// </summary>
+ bool IsDeleted { get; }
+
+ /// <summary>
+ /// Event that occurs when the text marker is deleted.
+ /// </summary>
+ event EventHandler Deleted;
+
+ /// <summary>
+ /// Gets/Sets the background color.
+ /// </summary>
+ Color? BackgroundColor { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the foreground color.
+ /// </summary>
+ Color? ForegroundColor { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the font weight.
+ /// </summary>
+ FontWeight? FontWeight { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the font style.
+ /// </summary>
+ FontStyle? FontStyle { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the type of the marker. Use TextMarkerType.None for normal markers.
+ /// </summary>
+ TextMarkerTypes MarkerTypes { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the color of the marker.
+ /// </summary>
+ Color MarkerColor { get; set; }
+
+ /// <summary>
+ /// Gets/Sets an object with additional data for this text marker.
+ /// </summary>
+ object Tag { get; set; }
+
+ /// <summary>
+ /// Gets/Sets an object that will be displayed as tooltip in the text editor.
+ /// </summary>
+ /// <remarks>Not supported in this sample!</remarks>
+ object ToolTip { get; set; }
+ }
+
+ [Flags]
+ public enum TextMarkerTypes
+ {
+ /// <summary>
+ /// Use no marker
+ /// </summary>
+ None = 0x0000,
+ /// <summary>
+ /// Use squiggly underline marker
+ /// </summary>
+ SquigglyUnderline = 0x001,
+ /// <summary>
+ /// Normal underline.
+ /// </summary>
+ NormalUnderline = 0x002,
+ /// <summary>
+ /// Dotted underline.
+ /// </summary>
+ DottedUnderline = 0x004,
+
+ /// <summary>
+ /// Horizontal line in the scroll bar.
+ /// </summary>
+ LineInScrollBar = 0x0100,
+ /// <summary>
+ /// Small triangle in the scroll bar, pointing to the right.
+ /// </summary>
+ ScrollBarRightTriangle = 0x0400,
+ /// <summary>
+ /// Small triangle in the scroll bar, pointing to the left.
+ /// </summary>
+ ScrollBarLeftTriangle = 0x0800,
+ /// <summary>
+ /// Small circle in the scroll bar.
+ /// </summary>
+ CircleInScrollBar = 0x1000
+ }
+
+ public interface ITextMarkerService
+ {
+ /// <summary>
+ /// 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.
+ /// </summary>
+ ITextMarker Create(int startOffset, int length);
+
+ /// <summary>
+ /// Gets the list of text markers.
+ /// </summary>
+ IEnumerable<ITextMarker> TextMarkers { get; }
+
+ /// <summary>
+ /// Removes the specified text marker.
+ /// </summary>
+ void Remove(ITextMarker marker);
+
+ /// <summary>
+ /// Removes all text markers that match the condition.
+ /// </summary>
+ void RemoveAll(Predicate<ITextMarker> predicate);
+
+ /// <summary>
+ /// Finds all text markers at the specified offset.
+ /// </summary>
+ IEnumerable<ITextMarker> 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
+{
+ /// <summary>
+ /// Handles the text markers for a code editor.
+ /// </summary>
+ public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService, ITextViewConnect
+ {
+ TextSegmentCollection<TextMarker> markers;
+ TextDocument document;
+
+ public TextMarkerService(TextDocument document)
+ {
+ if (document == null)
+ throw new ArgumentNullException("document");
+ this.document = document;
+ this.markers = new TextSegmentCollection<TextMarker>(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<ITextMarker> GetMarkersAtOffset(int offset)
+ {
+ if (markers == null)
+ return Enumerable.Empty<ITextMarker>();
+ else
+ return markers.FindSegmentsContaining(offset);
+ }
+
+ public IEnumerable<ITextMarker> TextMarkers {
+ get { return markers ?? Enumerable.Empty<ITextMarker>(); }
+ }
+
+ public void RemoveAll(Predicate<ITextMarker> 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();
+ }
+ }
+
+ /// <summary>
+ /// Redraws the specified text segment.
+ /// </summary>
+ 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<Point> 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<TextView> textViews = new List<TextView>();
+
+ 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<KnownType> _knownTypes;
private List<ScriptType> _declaredTypes;
private bool _isLoadingTypes;
+ private TextMarkerService errorMarkerService;
private static JsonSerializerSettings _jsonSettings;
private static Dictionary<Type, KnownType> _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 @@
<Compile Include="CodeCompletion\OverloadViewer.cs" />
<Compile Include="Converters\BooleanToVisibilityConverter.cs" />
<Compile Include="Converters\BooleanToVisibilityInversedConverter.cs" />
+ <Compile Include="Errors\ITextMarker.cs" />
+ <Compile Include="Errors\TextMarkerService.cs" />
<Compile Include="ExtensionMethods.cs" />
<Compile Include="Intellisense\ClassCompletionItemPopup.cs" />
<Compile Include="Intellisense\CompletionItem.cs" />