From 6463e83ca519892c94827d112bf4531272c1b55f Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Wed, 6 Mar 2019 17:45:17 +0200 Subject: Implemented Color Calibration on Machine Designer! --- .../Controls/MachineView.xaml | 268 +++++++++++++++++++++ .../Controls/MachineView.xaml.cs | 28 +++ .../Tango.MachineStudio.Common/Images/android.png | Bin 0 -> 1416 bytes .../Tango.MachineStudio.Common/Images/app.png | Bin 0 -> 1211 bytes .../Images/application-firmware.png | Bin 0 -> 953 bytes .../Images/cartridge.png | Bin 0 -> 818 bytes .../Images/dispenser.png | Bin 0 -> 11951 bytes .../Images/embedded-software.png | Bin 0 -> 768 bytes .../Tango.MachineStudio.Common/Images/embedded.png | Bin 0 -> 560 bytes .../Tango.MachineStudio.Common/Images/formula.png | Bin 0 -> 853 bytes .../Tango.MachineStudio.Common/Images/hardware.png | Bin 0 -> 1005 bytes .../Images/injection.png | Bin 0 -> 901 bytes .../Tango.MachineStudio.Common/Images/liquid.png | Bin 0 -> 1043 bytes .../Images/machine-full-fx.png | Bin 0 -> 217186 bytes .../Tango.MachineStudio.Common/Images/mid-tank.png | Bin 0 -> 2164 bytes .../Tango.MachineStudio.Common/Images/tablet.png | Bin 0 -> 792 bytes .../Tango.MachineStudio.Common/Images/tank.png | Bin 0 -> 684 bytes .../Images/ti-tm4c129x.png | Bin 0 -> 271765 bytes .../Tango.MachineStudio.Common.csproj | 62 ++++- 19 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml.cs create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/android.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/app.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/application-firmware.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/cartridge.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/dispenser.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded-software.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/formula.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/hardware.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/injection.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/liquid.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/machine-full-fx.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/mid-tank.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tablet.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tank.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/ti-tm4c129x.png (limited to 'Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common') diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml new file mode 100644 index 000000000..6f352f35c --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hardware + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NO IDS PACKS + + + + + + + + + + + + + + + + + + + + + + + + + Touch Panel + + + + + + Embedded Firmware + + + + + + Dispensers + + + + + + Mid Tanks + + + + + + Cartridges + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml.cs new file mode 100644 index 000000000..9d400549b --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/MachineView.xaml.cs @@ -0,0 +1,28 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.MachineStudio.Common.Controls +{ + /// + /// Interaction logic for MachineView.xaml + /// + public partial class MachineView : UserControl + { + public MachineView() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/android.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/android.png new file mode 100644 index 000000000..7fc5fbfe1 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/android.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/app.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/app.png new file mode 100644 index 000000000..9798ed866 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/app.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/application-firmware.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/application-firmware.png new file mode 100644 index 000000000..110ae36aa Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/application-firmware.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/cartridge.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/cartridge.png new file mode 100644 index 000000000..e3a9116fa Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/cartridge.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/dispenser.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/dispenser.png new file mode 100644 index 000000000..a070c94e0 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/dispenser.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded-software.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded-software.png new file mode 100644 index 000000000..b6a2087aa Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded-software.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded.png new file mode 100644 index 000000000..d0b917b44 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/embedded.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/formula.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/formula.png new file mode 100644 index 000000000..6f476e79c Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/formula.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/hardware.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/hardware.png new file mode 100644 index 000000000..856cf1ec7 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/hardware.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/injection.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/injection.png new file mode 100644 index 000000000..af1b3d575 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/injection.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/liquid.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/liquid.png new file mode 100644 index 000000000..7c5a4bceb Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/liquid.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/machine-full-fx.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/machine-full-fx.png new file mode 100644 index 000000000..b3c497546 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/machine-full-fx.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/mid-tank.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/mid-tank.png new file mode 100644 index 000000000..4da752a42 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/mid-tank.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tablet.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tablet.png new file mode 100644 index 000000000..1a09683eb Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tablet.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tank.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tank.png new file mode 100644 index 000000000..59440e1e5 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/tank.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/ti-tm4c129x.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/ti-tm4c129x.png new file mode 100644 index 000000000..e414e3355 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Images/ti-tm4c129x.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj index 2af5c37ec..bb059d67b 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj @@ -84,6 +84,9 @@ GlobalVersionInfo.cs + + MachineView.xaml + @@ -155,6 +158,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile @@ -328,7 +335,60 @@ True - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.3.1 From ac144491328ec237859baf4048e4d99f6f42139c Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Wed, 6 Mar 2019 18:25:07 +0200 Subject: Fixed some issues with firmware version upgrade. --- .../Tango.MachineStudio.Common.csproj | 8 +------- .../Tango.Integration/Operation/MachineOperator.cs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common') diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj index bb059d67b..4cf1855ca 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj @@ -368,15 +368,9 @@ - - - - - - @@ -392,7 +386,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Integration/Operation/MachineOperator.cs b/Software/Visual_Studio/Tango.Integration/Operation/MachineOperator.cs index d49dcc53c..c13961191 100644 --- a/Software/Visual_Studio/Tango.Integration/Operation/MachineOperator.cs +++ b/Software/Visual_Studio/Tango.Integration/Operation/MachineOperator.cs @@ -2048,10 +2048,13 @@ namespace Tango.Integration.Operation { bool cancel = false; ZipFile zip = null; + Action abortAction = null; var upgradeHandler = new FirmwareUpgradeHandler(() => { cancel = true; + + abortAction?.Invoke(); }); try @@ -2061,8 +2064,6 @@ namespace Tango.Integration.Operation throw LogManager.Log(new InvalidOperationException($"Could not perform firmware upgrade while operator status is '{Status}'.")); } - Status = MachineStatuses.Upgrading; - var package_info = await GetFirmwarePackageInfo(tfpStream); tfpStream.Position = 0; @@ -2094,6 +2095,13 @@ namespace Tango.Integration.Operation Action activate = null; Action postActivation = null; + Status = MachineStatuses.Upgrading; + + abortAction = new Action(() => + { + Status = MachineStatuses.ReadyToDye; + }); + upgradeDFU = new Action(() => { try @@ -2151,6 +2159,7 @@ namespace Tango.Integration.Operation } catch (Exception ex) { + Status = MachineStatuses.ReadyToDye; upgradeHandler.RaiseFailed(ex); return; } @@ -2170,9 +2179,9 @@ namespace Tango.Integration.Operation var handler = storage.UploadFile(Path.Combine(package_folder, entry.FileName), reader).Result; handlers.Add(handler); - handler.Canceled += (_, __) => { upgradeHandler.RaiseCanceled(); cancel = true; }; + handler.Canceled += (_, __) => { upgradeHandler.RaiseCanceled(); cancel = true; abortAction(); }; handler.Completed += (_, __) => uploadNext(); - handler.Failed += (_, failedEx) => { upgradeHandler.RaiseFailed(failedEx); cancel = true; }; + handler.Failed += (_, failedEx) => { upgradeHandler.RaiseFailed(failedEx); cancel = true; abortAction(); }; handler.Progress += (_, e) => { if (cancel) @@ -2186,6 +2195,7 @@ namespace Tango.Integration.Operation } catch (Exception ex) { + abortAction(); upgradeHandler.RaiseFailed(ex); } } -- cgit v1.3.1 From 08dd6000fe3a218221003876a699f448835b62e4 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Mon, 8 Apr 2019 00:44:51 +0300 Subject: Working on TCC... --- .gitignore | 3 + Software/DB/TCC/TCC.mdf | Bin 0 -> 8388608 bytes Software/DB/TCC/TCC_log.ldf | Bin 0 -> 8388608 bytes .../ZBar/bin/libMagickCore-2.dll | Bin 0 -> 3225283 bytes .../ZBar/bin/libMagickWand-2.dll | Bin 0 -> 1030247 bytes .../External_Repositories/ZBar/bin/libiconv-2.dll | Bin 0 -> 964989 bytes .../External_Repositories/ZBar/bin/libjpeg-7.dll | Bin 0 -> 234004 bytes .../External_Repositories/ZBar/bin/libpng12-0.dll | Bin 0 -> 187149 bytes .../External_Repositories/ZBar/bin/libtiff-3.dll | Bin 0 -> 375388 bytes .../External_Repositories/ZBar/bin/libxml2-2.dll | Bin 0 -> 1157984 bytes .../External_Repositories/ZBar/bin/libzbar-0.dll | Bin 0 -> 209438 bytes .../External_Repositories/ZBar/bin/zbarcam.bat | 9 + .../External_Repositories/ZBar/bin/zbarcam.exe | Bin 0 -> 30460 bytes .../External_Repositories/ZBar/bin/zbarimg.exe | Bin 0 -> 35548 bytes Software/External_Repositories/ZBar/bin/zlib1.dll | Bin 0 -> 78465 bytes Software/External_Repositories/ZBar/include/zbar.h | 1312 ++++++++++++++++++++ .../ZBar/include/zbar/Decoder.h | 193 +++ .../ZBar/include/zbar/Exception.h | 187 +++ .../ZBar/include/zbar/Image.h | 290 +++++ .../ZBar/include/zbar/ImageScanner.h | 130 ++ .../ZBar/include/zbar/Processor.h | 223 ++++ .../ZBar/include/zbar/Scanner.h | 162 +++ .../ZBar/include/zbar/Symbol.h | 451 +++++++ .../ZBar/include/zbar/Video.h | 170 +++ .../ZBar/include/zbar/Window.h | 136 ++ .../External_Repositories/ZBar/lib/libzbar-0.def | 120 ++ .../External_Repositories/ZBar/lib/libzbar-0.lib | Bin 0 -> 30120 bytes .../External_Repositories/ZBar/lib/libzbar.dll.a | Bin 0 -> 78184 bytes .../Build/Shortcuts/Machine Emulator.lnk | Bin 1445 -> 1530 bytes .../Build/Shortcuts/Machine Studio.lnk | Bin 1516 -> 1581 bytes .../Build/Shortcuts/Proto Compiler GUI.lnk | Bin 1292 -> 1529 bytes .../Build/Shortcuts/Stubs Execution GUI.lnk | Bin 1310 -> 1547 bytes .../Build/Shortcuts/Transport Router.lnk | Bin 1341 -> 1578 bytes .../Models/CaptureConfig.cs | 41 + .../Models/DeltaEComparisons.cs | 16 + .../Tango.MachineStudio.ColorCapture.csproj | 5 + .../ViewModels/MainViewVM.cs | 103 +- .../Views/MainView.xaml | 226 +++- .../Video/DefaultVideoCaptureProvider.cs | 17 +- .../Views/MachineConnectionView.xaml | 2 +- Software/Visual_Studio/TCC/Tango.TCC.BL/App.config | 45 + .../TCC/Tango.TCC.BL/CardDetectionConfig.cs | 6 + .../Tango.TCC.BL/CardDetectionHistogramMethods.cs | 17 + .../TCC/Tango.TCC.BL/CardDetectionResult.cs | 2 + .../Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs | 13 +- .../TCC/Tango.TCC.BL/ColorDetector.cs | 21 +- .../TCC/Tango.TCC.BL/Entities/Device.cs | 16 + .../TCC/Tango.TCC.BL/Entities/TCCContext.cs | 42 + .../TCC/Tango.TCC.BL/Entities/TCCEntityBase.cs | 18 + .../TCC/Tango.TCC.BL/Tango.TCC.BL.csproj | 17 + .../TCC/Tango.TCC.BL/Web/ColorDetectionRequest.cs | 2 +- .../TCC/Tango.TCC.BL/Web/ColorDetectionResponse.cs | 2 - .../TCC/Tango.TCC.BL/Web/DefinitionRequest.cs | 13 + .../TCC/Tango.TCC.BL/Web/DefinitionResponse.cs | 32 + .../TCC/Tango.TCC.BL/Web/LoginRequest.cs | 20 + .../TCC/Tango.TCC.BL/Web/LoginResponse.cs | 15 + .../Visual_Studio/TCC/Tango.TCC.BL/packages.config | 1 + .../TCC/Tango.TCC.CardDetector/ArucoUtils.cpp | 1 + .../TCC/Tango.TCC.CardDetector/CardDetection.cpp | Bin 6636 -> 10128 bytes .../TCC/Tango.TCC.CardDetector/CardDetection.h | Bin 1104 -> 1240 bytes .../Tango.TCC.CardDetector/CardDetectionConfig.h | 3 + .../Tango.TCC.CardDetector/CardDetectionResult.h | 2 + .../Tango.TCC.CardDetector.vcxproj | 6 +- .../Tango.TCC.OpenCV.DLL.csproj | 36 + .../Controllers/ColorDetectionController.cs | 27 +- .../TCC/Tango.TCC.Service/TCCServiceConfig.cs | 48 + .../TCC/Tango.TCC.Service/Tango.TCC.Service.csproj | 6 + .../Visual_Studio/TCC/Tango.TCC.Service/Web.config | 43 +- .../TCC/Tango.TCC.Service/packages.config | 1 + .../Tango.SharedUI/Controls/DoubleClickDataGrid.cs | 2 +- .../Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs | 19 + 71 files changed, 4190 insertions(+), 82 deletions(-) create mode 100644 Software/DB/TCC/TCC.mdf create mode 100644 Software/DB/TCC/TCC_log.ldf create mode 100644 Software/External_Repositories/ZBar/bin/libMagickCore-2.dll create mode 100644 Software/External_Repositories/ZBar/bin/libMagickWand-2.dll create mode 100644 Software/External_Repositories/ZBar/bin/libiconv-2.dll create mode 100644 Software/External_Repositories/ZBar/bin/libjpeg-7.dll create mode 100644 Software/External_Repositories/ZBar/bin/libpng12-0.dll create mode 100644 Software/External_Repositories/ZBar/bin/libtiff-3.dll create mode 100644 Software/External_Repositories/ZBar/bin/libxml2-2.dll create mode 100644 Software/External_Repositories/ZBar/bin/libzbar-0.dll create mode 100644 Software/External_Repositories/ZBar/bin/zbarcam.bat create mode 100644 Software/External_Repositories/ZBar/bin/zbarcam.exe create mode 100644 Software/External_Repositories/ZBar/bin/zbarimg.exe create mode 100644 Software/External_Repositories/ZBar/bin/zlib1.dll create mode 100644 Software/External_Repositories/ZBar/include/zbar.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Decoder.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Exception.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Image.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/ImageScanner.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Processor.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Scanner.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Symbol.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Video.h create mode 100644 Software/External_Repositories/ZBar/include/zbar/Window.h create mode 100644 Software/External_Repositories/ZBar/lib/libzbar-0.def create mode 100644 Software/External_Repositories/ZBar/lib/libzbar-0.lib create mode 100644 Software/External_Repositories/ZBar/lib/libzbar.dll.a create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/DeltaEComparisons.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/App.config create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionHistogramMethods.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/Device.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCContext.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCEntityBase.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionRequest.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginRequest.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginResponse.cs (limited to 'Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common') diff --git a/.gitignore b/.gitignore index 8b1c529db..041656ff8 100644 --- a/.gitignore +++ b/.gitignore @@ -305,3 +305,6 @@ __pycache__/ /Software/Visual_Studio/Advanced Installer Projects/Machine Studio Installer-cache /Software/Visual_Studio/Advanced Installer Projects/PPC Installer-cache /Software/Visual_Studio/Build + +# ZBar binaries +!/Software/External_Repositories/ZBar/bin diff --git a/Software/DB/TCC/TCC.mdf b/Software/DB/TCC/TCC.mdf new file mode 100644 index 000000000..f73a45d63 Binary files /dev/null and b/Software/DB/TCC/TCC.mdf differ diff --git a/Software/DB/TCC/TCC_log.ldf b/Software/DB/TCC/TCC_log.ldf new file mode 100644 index 000000000..53fbaa5d0 Binary files /dev/null and b/Software/DB/TCC/TCC_log.ldf differ diff --git a/Software/External_Repositories/ZBar/bin/libMagickCore-2.dll b/Software/External_Repositories/ZBar/bin/libMagickCore-2.dll new file mode 100644 index 000000000..fbb1fcfc5 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libMagickCore-2.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libMagickWand-2.dll b/Software/External_Repositories/ZBar/bin/libMagickWand-2.dll new file mode 100644 index 000000000..3e9a2cbf8 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libMagickWand-2.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libiconv-2.dll b/Software/External_Repositories/ZBar/bin/libiconv-2.dll new file mode 100644 index 000000000..f3368973d Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libiconv-2.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libjpeg-7.dll b/Software/External_Repositories/ZBar/bin/libjpeg-7.dll new file mode 100644 index 000000000..2cf6738b6 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libjpeg-7.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libpng12-0.dll b/Software/External_Repositories/ZBar/bin/libpng12-0.dll new file mode 100644 index 000000000..35e293002 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libpng12-0.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libtiff-3.dll b/Software/External_Repositories/ZBar/bin/libtiff-3.dll new file mode 100644 index 000000000..82706f644 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libtiff-3.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libxml2-2.dll b/Software/External_Repositories/ZBar/bin/libxml2-2.dll new file mode 100644 index 000000000..3ef041fcf Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libxml2-2.dll differ diff --git a/Software/External_Repositories/ZBar/bin/libzbar-0.dll b/Software/External_Repositories/ZBar/bin/libzbar-0.dll new file mode 100644 index 000000000..fbbbd2328 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/libzbar-0.dll differ diff --git a/Software/External_Repositories/ZBar/bin/zbarcam.bat b/Software/External_Repositories/ZBar/bin/zbarcam.bat new file mode 100644 index 000000000..acefe7b69 --- /dev/null +++ b/Software/External_Repositories/ZBar/bin/zbarcam.bat @@ -0,0 +1,9 @@ +@set PATH=%PATH%;C:\Program Files (x86)\ZBar\bin +@echo This is the zbarcam output window. +@echo Hold a bar code in front of the camera (make sure it's in focus!) +@echo and decoded results will appear below. +@echo. +@echo Initializing camera, please wait... +@echo. +@zbarcam.exe --prescale=640x480 +@if errorlevel 1 pause diff --git a/Software/External_Repositories/ZBar/bin/zbarcam.exe b/Software/External_Repositories/ZBar/bin/zbarcam.exe new file mode 100644 index 000000000..d2d2a06e8 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/zbarcam.exe differ diff --git a/Software/External_Repositories/ZBar/bin/zbarimg.exe b/Software/External_Repositories/ZBar/bin/zbarimg.exe new file mode 100644 index 000000000..179494326 Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/zbarimg.exe differ diff --git a/Software/External_Repositories/ZBar/bin/zlib1.dll b/Software/External_Repositories/ZBar/bin/zlib1.dll new file mode 100644 index 000000000..9752c129a Binary files /dev/null and b/Software/External_Repositories/ZBar/bin/zlib1.dll differ diff --git a/Software/External_Repositories/ZBar/include/zbar.h b/Software/External_Repositories/ZBar/include/zbar.h new file mode 100644 index 000000000..0ab1d6246 --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar.h @@ -0,0 +1,1312 @@ +/*------------------------------------------------------------------------ + * Copyright 2007-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ +#ifndef _ZBAR_H_ +#define _ZBAR_H_ + +/** @file + * ZBar Barcode Reader C API definition + */ + +/** @mainpage + * + * interface to the barcode reader is available at several levels. + * most applications will want to use the high-level interfaces: + * + * @section high-level High-Level Interfaces + * + * these interfaces wrap all library functionality into an easy-to-use + * package for a specific toolkit: + * - the "GTK+ 2.x widget" may be used with GTK GUI applications. a + * Python wrapper is included for PyGtk + * - the @ref zbar::QZBar "Qt4 widget" may be used with Qt GUI + * applications + * - the Processor interface (in @ref c-processor "C" or @ref + * zbar::Processor "C++") adds a scanning window to an application + * with no GUI. + * + * @section mid-level Intermediate Interfaces + * + * building blocks used to construct high-level interfaces: + * - the ImageScanner (in @ref c-imagescanner "C" or @ref + * zbar::ImageScanner "C++") looks for barcodes in a library defined + * image object + * - the Window abstraction (in @ref c-window "C" or @ref + * zbar::Window "C++") sinks library images, displaying them on the + * platform display + * - the Video abstraction (in @ref c-video "C" or @ref zbar::Video + * "C++") sources library images from a video device + * + * @section low-level Low-Level Interfaces + * + * direct interaction with barcode scanning and decoding: + * - the Scanner (in @ref c-scanner "C" or @ref zbar::Scanner "C++") + * looks for barcodes in a linear intensity sample stream + * - the Decoder (in @ref c-decoder "C" or @ref zbar::Decoder "C++") + * extracts barcodes from a stream of bar and space widths + */ + +#ifdef __cplusplus + +/** C++ namespace for library interfaces */ +namespace zbar { + extern "C" { +#endif + + +/** @name Global library interfaces */ +/*@{*/ + +/** "color" of element: bar or space. */ +typedef enum zbar_color_e { + ZBAR_SPACE = 0, /**< light area or space between bars */ + ZBAR_BAR = 1, /**< dark area or colored bar segment */ +} zbar_color_t; + +/** decoded symbol type. */ +typedef enum zbar_symbol_type_e { + ZBAR_NONE = 0, /**< no symbol decoded */ + ZBAR_PARTIAL = 1, /**< intermediate status */ + ZBAR_EAN8 = 8, /**< EAN-8 */ + ZBAR_UPCE = 9, /**< UPC-E */ + ZBAR_ISBN10 = 10, /**< ISBN-10 (from EAN-13). @since 0.4 */ + ZBAR_UPCA = 12, /**< UPC-A */ + ZBAR_EAN13 = 13, /**< EAN-13 */ + ZBAR_ISBN13 = 14, /**< ISBN-13 (from EAN-13). @since 0.4 */ + ZBAR_I25 = 25, /**< Interleaved 2 of 5. @since 0.4 */ + ZBAR_CODE39 = 39, /**< Code 39. @since 0.4 */ + ZBAR_PDF417 = 57, /**< PDF417. @since 0.6 */ + ZBAR_QRCODE = 64, /**< QR Code. @since 0.10 */ + ZBAR_CODE128 = 128, /**< Code 128 */ + ZBAR_SYMBOL = 0x00ff, /**< mask for base symbol type */ + ZBAR_ADDON2 = 0x0200, /**< 2-digit add-on flag */ + ZBAR_ADDON5 = 0x0500, /**< 5-digit add-on flag */ + ZBAR_ADDON = 0x0700, /**< add-on flag mask */ +} zbar_symbol_type_t; + +/** error codes. */ +typedef enum zbar_error_e { + ZBAR_OK = 0, /**< no error */ + ZBAR_ERR_NOMEM, /**< out of memory */ + ZBAR_ERR_INTERNAL, /**< internal library error */ + ZBAR_ERR_UNSUPPORTED, /**< unsupported request */ + ZBAR_ERR_INVALID, /**< invalid request */ + ZBAR_ERR_SYSTEM, /**< system error */ + ZBAR_ERR_LOCKING, /**< locking error */ + ZBAR_ERR_BUSY, /**< all resources busy */ + ZBAR_ERR_XDISPLAY, /**< X11 display error */ + ZBAR_ERR_XPROTO, /**< X11 protocol error */ + ZBAR_ERR_CLOSED, /**< output window is closed */ + ZBAR_ERR_WINAPI, /**< windows system error */ + ZBAR_ERR_NUM /**< number of error codes */ +} zbar_error_t; + +/** decoder configuration options. + * @since 0.4 + */ +typedef enum zbar_config_e { + ZBAR_CFG_ENABLE = 0, /**< enable symbology/feature */ + ZBAR_CFG_ADD_CHECK, /**< enable check digit when optional */ + ZBAR_CFG_EMIT_CHECK, /**< return check digit when present */ + ZBAR_CFG_ASCII, /**< enable full ASCII character set */ + ZBAR_CFG_NUM, /**< number of boolean decoder configs */ + + ZBAR_CFG_MIN_LEN = 0x20, /**< minimum data length for valid decode */ + ZBAR_CFG_MAX_LEN, /**< maximum data length for valid decode */ + + ZBAR_CFG_POSITION = 0x80, /**< enable scanner to collect position data */ + + ZBAR_CFG_X_DENSITY = 0x100, /**< image scanner vertical scan density */ + ZBAR_CFG_Y_DENSITY, /**< image scanner horizontal scan density */ +} zbar_config_t; + +/** retrieve runtime library version information. + * @param major set to the running major version (unless NULL) + * @param minor set to the running minor version (unless NULL) + * @returns 0 + */ +extern int zbar_version(unsigned *major, + unsigned *minor); + +/** set global library debug level. + * @param verbosity desired debug level. higher values create more spew + */ +extern void zbar_set_verbosity(int verbosity); + +/** increase global library debug level. + * eg, for -vvvv + */ +extern void zbar_increase_verbosity(void); + +/** retrieve string name for symbol encoding. + * @param sym symbol type encoding + * @returns the static string name for the specified symbol type, + * or "UNKNOWN" if the encoding is not recognized + */ +extern const char *zbar_get_symbol_name(zbar_symbol_type_t sym); + +/** retrieve string name for addon encoding. + * @param sym symbol type encoding + * @returns static string name for any addon, or the empty string + * if no addons were decoded + */ +extern const char *zbar_get_addon_name(zbar_symbol_type_t sym); + +/** parse a configuration string of the form "[symbology.]config[=value]". + * the config must match one of the recognized names. + * the symbology, if present, must match one of the recognized names. + * if symbology is unspecified, it will be set to 0. + * if value is unspecified it will be set to 1. + * @returns 0 if the config is parsed successfully, 1 otherwise + * @since 0.4 + */ +extern int zbar_parse_config(const char *config_string, + zbar_symbol_type_t *symbology, + zbar_config_t *config, + int *value); + +/** @internal type unsafe error API (don't use) */ +extern int _zbar_error_spew(const void *object, + int verbosity); +extern const char *_zbar_error_string(const void *object, + int verbosity); +extern zbar_error_t _zbar_get_error_code(const void *object); + +/*@}*/ + +struct zbar_symbol_s; +typedef struct zbar_symbol_s zbar_symbol_t; + +struct zbar_symbol_set_s; +typedef struct zbar_symbol_set_s zbar_symbol_set_t; + + +/*------------------------------------------------------------*/ +/** @name Symbol interface + * decoded barcode symbol result object. stores type, data, and image + * location of decoded symbol. all memory is owned by the library + */ +/*@{*/ + +/** @typedef zbar_symbol_t + * opaque decoded symbol object. + */ + +/** symbol reference count manipulation. + * increment the reference count when you store a new reference to the + * symbol. decrement when the reference is no longer used. do not + * refer to the symbol once the count is decremented and the + * containing image has been recycled or destroyed. + * @note the containing image holds a reference to the symbol, so you + * only need to use this if you keep a symbol after the image has been + * destroyed or reused. + * @since 0.9 + */ +extern void zbar_symbol_ref(const zbar_symbol_t *symbol, + int refs); + +/** retrieve type of decoded symbol. + * @returns the symbol type + */ +extern zbar_symbol_type_t zbar_symbol_get_type(const zbar_symbol_t *symbol); + +/** retrieve data decoded from symbol. + * @returns the data string + */ +extern const char *zbar_symbol_get_data(const zbar_symbol_t *symbol); + +/** retrieve length of binary data. + * @returns the length of the decoded data + */ +extern unsigned int zbar_symbol_get_data_length(const zbar_symbol_t *symbol); + +/** retrieve a symbol confidence metric. + * @returns an unscaled, relative quantity: larger values are better + * than smaller values, where "large" and "small" are application + * dependent. + * @note expect the exact definition of this quantity to change as the + * metric is refined. currently, only the ordered relationship + * between two values is defined and will remain stable in the future + * @since 0.9 + */ +extern int zbar_symbol_get_quality(const zbar_symbol_t *symbol); + +/** retrieve current cache count. when the cache is enabled for the + * image_scanner this provides inter-frame reliability and redundancy + * information for video streams. + * @returns < 0 if symbol is still uncertain. + * @returns 0 if symbol is newly verified. + * @returns > 0 for duplicate symbols + */ +extern int zbar_symbol_get_count(const zbar_symbol_t *symbol); + +/** retrieve the number of points in the location polygon. the + * location polygon defines the image area that the symbol was + * extracted from. + * @returns the number of points in the location polygon + * @note this is currently not a polygon, but the scan locations + * where the symbol was decoded + */ +extern unsigned zbar_symbol_get_loc_size(const zbar_symbol_t *symbol); + +/** retrieve location polygon x-coordinates. + * points are specified by 0-based index. + * @returns the x-coordinate for a point in the location polygon. + * @returns -1 if index is out of range + */ +extern int zbar_symbol_get_loc_x(const zbar_symbol_t *symbol, + unsigned index); + +/** retrieve location polygon y-coordinates. + * points are specified by 0-based index. + * @returns the y-coordinate for a point in the location polygon. + * @returns -1 if index is out of range + */ +extern int zbar_symbol_get_loc_y(const zbar_symbol_t *symbol, + unsigned index); + +/** iterate the set to which this symbol belongs (there can be only one). + * @returns the next symbol in the set, or + * @returns NULL when no more results are available + */ +extern const zbar_symbol_t *zbar_symbol_next(const zbar_symbol_t *symbol); + +/** retrieve components of a composite result. + * @returns the symbol set containing the components + * @returns NULL if the symbol is already a physical symbol + * @since 0.10 + */ +extern const zbar_symbol_set_t* +zbar_symbol_get_components(const zbar_symbol_t *symbol); + +/** iterate components of a composite result. + * @returns the first physical component symbol of a composite result + * @returns NULL if the symbol is already a physical symbol + * @since 0.10 + */ +extern const zbar_symbol_t* +zbar_symbol_first_component(const zbar_symbol_t *symbol); + +/** print XML symbol element representation to user result buffer. + * @see http://zbar.sourceforge.net/2008/barcode.xsd for the schema. + * @param symbol is the symbol to print + * @param buffer is the inout result pointer, it will be reallocated + * with a larger size if necessary. + * @param buflen is inout length of the result buffer. + * @returns the buffer pointer + * @since 0.6 + */ +extern char *zbar_symbol_xml(const zbar_symbol_t *symbol, + char **buffer, + unsigned *buflen); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Symbol Set interface + * container for decoded result symbols associated with an image + * or a composite symbol. + * @since 0.10 + */ +/*@{*/ + +/** @typedef zbar_symbol_set_t + * opaque symbol iterator object. + * @since 0.10 + */ + +/** reference count manipulation. + * increment the reference count when you store a new reference. + * decrement when the reference is no longer used. do not refer to + * the object any longer once references have been released. + * @since 0.10 + */ +extern void zbar_symbol_set_ref(const zbar_symbol_set_t *symbols, + int refs); + +/** retrieve set size. + * @returns the number of symbols in the set. + * @since 0.10 + */ +extern int zbar_symbol_set_get_size(const zbar_symbol_set_t *symbols); + +/** set iterator. + * @returns the first decoded symbol result in a set + * @returns NULL if the set is empty + * @since 0.10 + */ +extern const zbar_symbol_t* +zbar_symbol_set_first_symbol(const zbar_symbol_set_t *symbols); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Image interface + * stores image data samples along with associated format and size + * metadata + */ +/*@{*/ + +struct zbar_image_s; +/** opaque image object. */ +typedef struct zbar_image_s zbar_image_t; + +/** cleanup handler callback function. + * called to free sample data when an image is destroyed. + */ +typedef void (zbar_image_cleanup_handler_t)(zbar_image_t *image); + +/** data handler callback function. + * called when decoded symbol results are available for an image + */ +typedef void (zbar_image_data_handler_t)(zbar_image_t *image, + const void *userdata); + +/** new image constructor. + * @returns a new image object with uninitialized data and format. + * this image should be destroyed (using zbar_image_destroy()) as + * soon as the application is finished with it + */ +extern zbar_image_t *zbar_image_create(void); + +/** image destructor. all images created by or returned to the + * application should be destroyed using this function. when an image + * is destroyed, the associated data cleanup handler will be invoked + * if available + * @note make no assumptions about the image or the data buffer. + * they may not be destroyed/cleaned immediately if the library + * is still using them. if necessary, use the cleanup handler hook + * to keep track of image data buffers + */ +extern void zbar_image_destroy(zbar_image_t *image); + +/** image reference count manipulation. + * increment the reference count when you store a new reference to the + * image. decrement when the reference is no longer used. do not + * refer to the image any longer once the count is decremented. + * zbar_image_ref(image, -1) is the same as zbar_image_destroy(image) + * @since 0.5 + */ +extern void zbar_image_ref(zbar_image_t *image, + int refs); + +/** image format conversion. refer to the documentation for supported + * image formats + * @returns a @em new image with the sample data from the original image + * converted to the requested format. the original image is + * unaffected. + * @note the converted image size may be rounded (up) due to format + * constraints + */ +extern zbar_image_t *zbar_image_convert(const zbar_image_t *image, + unsigned long format); + +/** image format conversion with crop/pad. + * if the requested size is larger than the image, the last row/column + * are duplicated to cover the difference. if the requested size is + * smaller than the image, the extra rows/columns are dropped from the + * right/bottom. + * @returns a @em new image with the sample data from the original + * image converted to the requested format and size. + * @note the image is @em not scaled + * @see zbar_image_convert() + * @since 0.4 + */ +extern zbar_image_t *zbar_image_convert_resize(const zbar_image_t *image, + unsigned long format, + unsigned width, + unsigned height); + +/** retrieve the image format. + * @returns the fourcc describing the format of the image sample data + */ +extern unsigned long zbar_image_get_format(const zbar_image_t *image); + +/** retrieve a "sequence" (page/frame) number associated with this image. + * @since 0.6 + */ +extern unsigned zbar_image_get_sequence(const zbar_image_t *image); + +/** retrieve the width of the image. + * @returns the width in sample columns + */ +extern unsigned zbar_image_get_width(const zbar_image_t *image); + +/** retrieve the height of the image. + * @returns the height in sample rows + */ +extern unsigned zbar_image_get_height(const zbar_image_t *image); + +/** return the image sample data. the returned data buffer is only + * valid until zbar_image_destroy() is called + */ +extern const void *zbar_image_get_data(const zbar_image_t *image); + +/** return the size of image data. + * @since 0.6 + */ +extern unsigned long zbar_image_get_data_length(const zbar_image_t *img); + +/** retrieve the decoded results. + * @returns the (possibly empty) set of decoded symbols + * @returns NULL if the image has not been scanned + * @since 0.10 + */ +extern const zbar_symbol_set_t* +zbar_image_get_symbols(const zbar_image_t *image); + +/** associate the specified symbol set with the image, replacing any + * existing results. use NULL to release the current results from the + * image. + * @see zbar_image_scanner_recycle_image() + * @since 0.10 + */ +extern void zbar_image_set_symbols(zbar_image_t *image, + const zbar_symbol_set_t *symbols); + +/** image_scanner decode result iterator. + * @returns the first decoded symbol result for an image + * or NULL if no results are available + */ +extern const zbar_symbol_t* +zbar_image_first_symbol(const zbar_image_t *image); + +/** specify the fourcc image format code for image sample data. + * refer to the documentation for supported formats. + * @note this does not convert the data! + * (see zbar_image_convert() for that) + */ +extern void zbar_image_set_format(zbar_image_t *image, + unsigned long format); + +/** associate a "sequence" (page/frame) number with this image. + * @since 0.6 + */ +extern void zbar_image_set_sequence(zbar_image_t *image, + unsigned sequence_num); + +/** specify the pixel size of the image. + * @note this does not affect the data! + */ +extern void zbar_image_set_size(zbar_image_t *image, + unsigned width, + unsigned height); + +/** specify image sample data. when image data is no longer needed by + * the library the specific data cleanup handler will be called + * (unless NULL) + * @note application image data will not be modified by the library + */ +extern void zbar_image_set_data(zbar_image_t *image, + const void *data, + unsigned long data_byte_length, + zbar_image_cleanup_handler_t *cleanup_hndlr); + +/** built-in cleanup handler. + * passes the image data buffer to free() + */ +extern void zbar_image_free_data(zbar_image_t *image); + +/** associate user specified data value with an image. + * @since 0.5 + */ +extern void zbar_image_set_userdata(zbar_image_t *image, + void *userdata); + +/** return user specified data value associated with the image. + * @since 0.5 + */ +extern void *zbar_image_get_userdata(const zbar_image_t *image); + +/** dump raw image data to a file for debug. + * the data will be prefixed with a 16 byte header consisting of: + * - 4 bytes uint = 0x676d697a ("zimg") + * - 4 bytes format fourcc + * - 2 bytes width + * - 2 bytes height + * - 4 bytes size of following image data in bytes + * this header can be dumped w/eg: + * @verbatim + od -Ax -tx1z -N16 -w4 [file] +@endverbatim + * for some formats the image can be displayed/converted using + * ImageMagick, eg: + * @verbatim + display -size 640x480+16 [-depth ?] [-sampling-factor ?x?] \ + {GRAY,RGB,UYVY,YUV}:[file] +@endverbatim + * + * @param image the image object to dump + * @param filebase base filename, appended with ".XXXX.zimg" where + * XXXX is the format fourcc + * @returns 0 on success or a system error code on failure + */ +extern int zbar_image_write(const zbar_image_t *image, + const char *filebase); + +/** read back an image in the format written by zbar_image_write() + * @note TBD + */ +extern zbar_image_t *zbar_image_read(char *filename); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Processor interface + * @anchor c-processor + * high-level self-contained image processor. + * processes video and images for barcodes, optionally displaying + * images to a library owned output window + */ +/*@{*/ + +struct zbar_processor_s; +/** opaque standalone processor object. */ +typedef struct zbar_processor_s zbar_processor_t; + +/** constructor. + * if threaded is set and threading is available the processor + * will spawn threads where appropriate to avoid blocking and + * improve responsiveness + */ +extern zbar_processor_t *zbar_processor_create(int threaded); + +/** destructor. cleans up all resources associated with the processor + */ +extern void zbar_processor_destroy(zbar_processor_t *processor); + +/** (re)initialization. + * opens a video input device and/or prepares to display output + */ +extern int zbar_processor_init(zbar_processor_t *processor, + const char *video_device, + int enable_display); + +/** request a preferred size for the video image from the device. + * the request may be adjusted or completely ignored by the driver. + * @note must be called before zbar_processor_init() + * @since 0.6 + */ +extern int zbar_processor_request_size(zbar_processor_t *processor, + unsigned width, + unsigned height); + +/** request a preferred video driver interface version for + * debug/testing. + * @note must be called before zbar_processor_init() + * @since 0.6 + */ +extern int zbar_processor_request_interface(zbar_processor_t *processor, + int version); + +/** request a preferred video I/O mode for debug/testing. You will + * get errors if the driver does not support the specified mode. + * @verbatim + 0 = auto-detect + 1 = force I/O using read() + 2 = force memory mapped I/O using mmap() + 3 = force USERPTR I/O (v4l2 only) +@endverbatim + * @note must be called before zbar_processor_init() + * @since 0.7 + */ +extern int zbar_processor_request_iomode(zbar_processor_t *video, + int iomode); + +/** force specific input and output formats for debug/testing. + * @note must be called before zbar_processor_init() + */ +extern int zbar_processor_force_format(zbar_processor_t *processor, + unsigned long input_format, + unsigned long output_format); + +/** setup result handler callback. + * the specified function will be called by the processor whenever + * new results are available from the video stream or a static image. + * pass a NULL value to disable callbacks. + * @param processor the object on which to set the handler. + * @param handler the function to call when new results are available. + * @param userdata is set as with zbar_processor_set_userdata(). + * @returns the previously registered handler + */ +extern zbar_image_data_handler_t* +zbar_processor_set_data_handler(zbar_processor_t *processor, + zbar_image_data_handler_t *handler, + const void *userdata); + +/** associate user specified data value with the processor. + * @since 0.6 + */ +extern void zbar_processor_set_userdata(zbar_processor_t *processor, + void *userdata); + +/** return user specified data value associated with the processor. + * @since 0.6 + */ +extern void *zbar_processor_get_userdata(const zbar_processor_t *processor); + +/** set config for indicated symbology (0 for all) to specified value. + * @returns 0 for success, non-0 for failure (config does not apply to + * specified symbology, or value out of range) + * @see zbar_decoder_set_config() + * @since 0.4 + */ +extern int zbar_processor_set_config(zbar_processor_t *processor, + zbar_symbol_type_t symbology, + zbar_config_t config, + int value); + +/** parse configuration string using zbar_parse_config() + * and apply to processor using zbar_processor_set_config(). + * @returns 0 for success, non-0 for failure + * @see zbar_parse_config() + * @see zbar_processor_set_config() + * @since 0.4 + */ +static inline int zbar_processor_parse_config (zbar_processor_t *processor, + const char *config_string) +{ + zbar_symbol_type_t sym; + zbar_config_t cfg; + int val; + return(zbar_parse_config(config_string, &sym, &cfg, &val) || + zbar_processor_set_config(processor, sym, cfg, val)); +} + +/** retrieve the current state of the ouput window. + * @returns 1 if the output window is currently displayed, 0 if not. + * @returns -1 if an error occurs + */ +extern int zbar_processor_is_visible(zbar_processor_t *processor); + +/** show or hide the display window owned by the library. + * the size will be adjusted to the input size + */ +extern int zbar_processor_set_visible(zbar_processor_t *processor, + int visible); + +/** control the processor in free running video mode. + * only works if video input is initialized. if threading is in use, + * scanning will occur in the background, otherwise this is only + * useful wrapping calls to zbar_processor_user_wait(). if the + * library output window is visible, video display will be enabled. + */ +extern int zbar_processor_set_active(zbar_processor_t *processor, + int active); + +/** retrieve decode results for last scanned image/frame. + * @returns the symbol set result container or NULL if no results are + * available + * @note the returned symbol set has its reference count incremented; + * ensure that the count is decremented after use + * @since 0.10 + */ +extern const zbar_symbol_set_t* +zbar_processor_get_results(const zbar_processor_t *processor); + +/** wait for input to the display window from the user + * (via mouse or keyboard). + * @returns >0 when input is received, 0 if timeout ms expired + * with no input or -1 in case of an error + */ +extern int zbar_processor_user_wait(zbar_processor_t *processor, + int timeout); + +/** process from the video stream until a result is available, + * or the timeout (in milliseconds) expires. + * specify a timeout of -1 to scan indefinitely + * (zbar_processor_set_active() may still be used to abort the scan + * from another thread). + * if the library window is visible, video display will be enabled. + * @note that multiple results may still be returned (despite the + * name). + * @returns >0 if symbols were successfully decoded, + * 0 if no symbols were found (ie, the timeout expired) + * or -1 if an error occurs + */ +extern int zbar_process_one(zbar_processor_t *processor, + int timeout); + +/** process the provided image for barcodes. + * if the library window is visible, the image will be displayed. + * @returns >0 if symbols were successfully decoded, + * 0 if no symbols were found or -1 if an error occurs + */ +extern int zbar_process_image(zbar_processor_t *processor, + zbar_image_t *image); + +/** display detail for last processor error to stderr. + * @returns a non-zero value suitable for passing to exit() + */ +static inline int +zbar_processor_error_spew (const zbar_processor_t *processor, + int verbosity) +{ + return(_zbar_error_spew(processor, verbosity)); +} + +/** retrieve the detail string for the last processor error. */ +static inline const char* +zbar_processor_error_string (const zbar_processor_t *processor, + int verbosity) +{ + return(_zbar_error_string(processor, verbosity)); +} + +/** retrieve the type code for the last processor error. */ +static inline zbar_error_t +zbar_processor_get_error_code (const zbar_processor_t *processor) +{ + return(_zbar_get_error_code(processor)); +} + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Video interface + * @anchor c-video + * mid-level video source abstraction. + * captures images from a video device + */ +/*@{*/ + +struct zbar_video_s; +/** opaque video object. */ +typedef struct zbar_video_s zbar_video_t; + +/** constructor. */ +extern zbar_video_t *zbar_video_create(void); + +/** destructor. */ +extern void zbar_video_destroy(zbar_video_t *video); + +/** open and probe a video device. + * the device specified by platform specific unique name + * (v4l device node path in *nix eg "/dev/video", + * DirectShow DevicePath property in windows). + * @returns 0 if successful or -1 if an error occurs + */ +extern int zbar_video_open(zbar_video_t *video, + const char *device); + +/** retrieve file descriptor associated with open *nix video device + * useful for using select()/poll() to tell when new images are + * available (NB v4l2 only!!). + * @returns the file descriptor or -1 if the video device is not open + * or the driver only supports v4l1 + */ +extern int zbar_video_get_fd(const zbar_video_t *video); + +/** request a preferred size for the video image from the device. + * the request may be adjusted or completely ignored by the driver. + * @returns 0 if successful or -1 if the video device is already + * initialized + * @since 0.6 + */ +extern int zbar_video_request_size(zbar_video_t *video, + unsigned width, + unsigned height); + +/** request a preferred driver interface version for debug/testing. + * @note must be called before zbar_video_open() + * @since 0.6 + */ +extern int zbar_video_request_interface(zbar_video_t *video, + int version); + +/** request a preferred I/O mode for debug/testing. You will get + * errors if the driver does not support the specified mode. + * @verbatim + 0 = auto-detect + 1 = force I/O using read() + 2 = force memory mapped I/O using mmap() + 3 = force USERPTR I/O (v4l2 only) +@endverbatim + * @note must be called before zbar_video_open() + * @since 0.7 + */ +extern int zbar_video_request_iomode(zbar_video_t *video, + int iomode); + +/** retrieve current output image width. + * @returns the width or 0 if the video device is not open + */ +extern int zbar_video_get_width(const zbar_video_t *video); + +/** retrieve current output image height. + * @returns the height or 0 if the video device is not open + */ +extern int zbar_video_get_height(const zbar_video_t *video); + +/** initialize video using a specific format for debug. + * use zbar_negotiate_format() to automatically select and initialize + * the best available format + */ +extern int zbar_video_init(zbar_video_t *video, + unsigned long format); + +/** start/stop video capture. + * all buffered images are retired when capture is disabled. + * @returns 0 if successful or -1 if an error occurs + */ +extern int zbar_video_enable(zbar_video_t *video, + int enable); + +/** retrieve next captured image. blocks until an image is available. + * @returns NULL if video is not enabled or an error occurs + */ +extern zbar_image_t *zbar_video_next_image(zbar_video_t *video); + +/** display detail for last video error to stderr. + * @returns a non-zero value suitable for passing to exit() + */ +static inline int zbar_video_error_spew (const zbar_video_t *video, + int verbosity) +{ + return(_zbar_error_spew(video, verbosity)); +} + +/** retrieve the detail string for the last video error. */ +static inline const char *zbar_video_error_string (const zbar_video_t *video, + int verbosity) +{ + return(_zbar_error_string(video, verbosity)); +} + +/** retrieve the type code for the last video error. */ +static inline zbar_error_t +zbar_video_get_error_code (const zbar_video_t *video) +{ + return(_zbar_get_error_code(video)); +} + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Window interface + * @anchor c-window + * mid-level output window abstraction. + * displays images to user-specified platform specific output window + */ +/*@{*/ + +struct zbar_window_s; +/** opaque window object. */ +typedef struct zbar_window_s zbar_window_t; + +/** constructor. */ +extern zbar_window_t *zbar_window_create(void); + +/** destructor. */ +extern void zbar_window_destroy(zbar_window_t *window); + +/** associate reader with an existing platform window. + * This can be any "Drawable" for X Windows or a "HWND" for windows. + * input images will be scaled into the output window. + * pass NULL to detach from the resource, further input will be + * ignored + */ +extern int zbar_window_attach(zbar_window_t *window, + void *x11_display_w32_hwnd, + unsigned long x11_drawable); + +/** control content level of the reader overlay. + * the overlay displays graphical data for informational or debug + * purposes. higher values increase the level of annotation (possibly + * decreasing performance). @verbatim + 0 = disable overlay + 1 = outline decoded symbols (default) + 2 = also track and display input frame rate +@endverbatim + */ +extern void zbar_window_set_overlay(zbar_window_t *window, + int level); + +/** retrieve current content level of reader overlay. + * @see zbar_window_set_overlay() + * @since 0.10 + */ +extern int zbar_window_get_overlay(const zbar_window_t *window); + +/** draw a new image into the output window. */ +extern int zbar_window_draw(zbar_window_t *window, + zbar_image_t *image); + +/** redraw the last image (exposure handler). */ +extern int zbar_window_redraw(zbar_window_t *window); + +/** resize the image window (reconfigure handler). + * this does @em not update the contents of the window + * @since 0.3, changed in 0.4 to not redraw window + */ +extern int zbar_window_resize(zbar_window_t *window, + unsigned width, + unsigned height); + +/** display detail for last window error to stderr. + * @returns a non-zero value suitable for passing to exit() + */ +static inline int zbar_window_error_spew (const zbar_window_t *window, + int verbosity) +{ + return(_zbar_error_spew(window, verbosity)); +} + +/** retrieve the detail string for the last window error. */ +static inline const char* +zbar_window_error_string (const zbar_window_t *window, + int verbosity) +{ + return(_zbar_error_string(window, verbosity)); +} + +/** retrieve the type code for the last window error. */ +static inline zbar_error_t +zbar_window_get_error_code (const zbar_window_t *window) +{ + return(_zbar_get_error_code(window)); +} + + +/** select a compatible format between video input and output window. + * the selection algorithm attempts to use a format shared by + * video input and window output which is also most useful for + * barcode scanning. if a format conversion is necessary, it will + * heuristically attempt to minimize the cost of the conversion + */ +extern int zbar_negotiate_format(zbar_video_t *video, + zbar_window_t *window); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Image Scanner interface + * @anchor c-imagescanner + * mid-level image scanner interface. + * reads barcodes from 2-D images + */ +/*@{*/ + +struct zbar_image_scanner_s; +/** opaque image scanner object. */ +typedef struct zbar_image_scanner_s zbar_image_scanner_t; + +/** constructor. */ +extern zbar_image_scanner_t *zbar_image_scanner_create(void); + +/** destructor. */ +extern void zbar_image_scanner_destroy(zbar_image_scanner_t *scanner); + +/** setup result handler callback. + * the specified function will be called by the scanner whenever + * new results are available from a decoded image. + * pass a NULL value to disable callbacks. + * @returns the previously registered handler + */ +extern zbar_image_data_handler_t* +zbar_image_scanner_set_data_handler(zbar_image_scanner_t *scanner, + zbar_image_data_handler_t *handler, + const void *userdata); + + +/** set config for indicated symbology (0 for all) to specified value. + * @returns 0 for success, non-0 for failure (config does not apply to + * specified symbology, or value out of range) + * @see zbar_decoder_set_config() + * @since 0.4 + */ +extern int zbar_image_scanner_set_config(zbar_image_scanner_t *scanner, + zbar_symbol_type_t symbology, + zbar_config_t config, + int value); + +/** parse configuration string using zbar_parse_config() + * and apply to image scanner using zbar_image_scanner_set_config(). + * @returns 0 for success, non-0 for failure + * @see zbar_parse_config() + * @see zbar_image_scanner_set_config() + * @since 0.4 + */ +static inline int +zbar_image_scanner_parse_config (zbar_image_scanner_t *scanner, + const char *config_string) +{ + zbar_symbol_type_t sym; + zbar_config_t cfg; + int val; + return(zbar_parse_config(config_string, &sym, &cfg, &val) || + zbar_image_scanner_set_config(scanner, sym, cfg, val)); +} + +/** enable or disable the inter-image result cache (default disabled). + * mostly useful for scanning video frames, the cache filters + * duplicate results from consecutive images, while adding some + * consistency checking and hysteresis to the results. + * this interface also clears the cache + */ +extern void zbar_image_scanner_enable_cache(zbar_image_scanner_t *scanner, + int enable); + +/** remove any previously decoded results from the image scanner and the + * specified image. somewhat more efficient version of + * zbar_image_set_symbols(image, NULL) which may retain memory for + * subsequent decodes + * @since 0.10 + */ +extern void zbar_image_scanner_recycle_image(zbar_image_scanner_t *scanner, + zbar_image_t *image); + +/** retrieve decode results for last scanned image. + * @returns the symbol set result container or NULL if no results are + * available + * @note the symbol set does not have its reference count adjusted; + * ensure that the count is incremented if the results may be kept + * after the next image is scanned + * @since 0.10 + */ +extern const zbar_symbol_set_t* +zbar_image_scanner_get_results(const zbar_image_scanner_t *scanner); + +/** scan for symbols in provided image. The image format must be + * "Y800" or "GRAY". + * @returns >0 if symbols were successfully decoded from the image, + * 0 if no symbols were found or -1 if an error occurs + * @see zbar_image_convert() + * @since 0.9 - changed to only accept grayscale images + */ +extern int zbar_scan_image(zbar_image_scanner_t *scanner, + zbar_image_t *image); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Decoder interface + * @anchor c-decoder + * low-level bar width stream decoder interface. + * identifies symbols and extracts encoded data + */ +/*@{*/ + +struct zbar_decoder_s; +/** opaque decoder object. */ +typedef struct zbar_decoder_s zbar_decoder_t; + +/** decoder data handler callback function. + * called by decoder when new data has just been decoded + */ +typedef void (zbar_decoder_handler_t)(zbar_decoder_t *decoder); + +/** constructor. */ +extern zbar_decoder_t *zbar_decoder_create(void); + +/** destructor. */ +extern void zbar_decoder_destroy(zbar_decoder_t *decoder); + +/** set config for indicated symbology (0 for all) to specified value. + * @returns 0 for success, non-0 for failure (config does not apply to + * specified symbology, or value out of range) + * @since 0.4 + */ +extern int zbar_decoder_set_config(zbar_decoder_t *decoder, + zbar_symbol_type_t symbology, + zbar_config_t config, + int value); + +/** parse configuration string using zbar_parse_config() + * and apply to decoder using zbar_decoder_set_config(). + * @returns 0 for success, non-0 for failure + * @see zbar_parse_config() + * @see zbar_decoder_set_config() + * @since 0.4 + */ +static inline int zbar_decoder_parse_config (zbar_decoder_t *decoder, + const char *config_string) +{ + zbar_symbol_type_t sym; + zbar_config_t cfg; + int val; + return(zbar_parse_config(config_string, &sym, &cfg, &val) || + zbar_decoder_set_config(decoder, sym, cfg, val)); +} + +/** clear all decoder state. + * any partial symbols are flushed + */ +extern void zbar_decoder_reset(zbar_decoder_t *decoder); + +/** mark start of a new scan pass. + * clears any intra-symbol state and resets color to ::ZBAR_SPACE. + * any partially decoded symbol state is retained + */ +extern void zbar_decoder_new_scan(zbar_decoder_t *decoder); + +/** process next bar/space width from input stream. + * the width is in arbitrary relative units. first value of a scan + * is ::ZBAR_SPACE width, alternating from there. + * @returns appropriate symbol type if width completes + * decode of a symbol (data is available for retrieval) + * @returns ::ZBAR_PARTIAL as a hint if part of a symbol was decoded + * @returns ::ZBAR_NONE (0) if no new symbol data is available + */ +extern zbar_symbol_type_t zbar_decode_width(zbar_decoder_t *decoder, + unsigned width); + +/** retrieve color of @em next element passed to + * zbar_decode_width(). */ +extern zbar_color_t zbar_decoder_get_color(const zbar_decoder_t *decoder); + +/** retrieve last decoded data. + * @returns the data string or NULL if no new data available. + * the returned data buffer is owned by library, contents are only + * valid between non-0 return from zbar_decode_width and next library + * call + */ +extern const char *zbar_decoder_get_data(const zbar_decoder_t *decoder); + +/** retrieve length of binary data. + * @returns the length of the decoded data or 0 if no new data + * available. + */ +extern unsigned int +zbar_decoder_get_data_length(const zbar_decoder_t *decoder); + +/** retrieve last decoded symbol type. + * @returns the type or ::ZBAR_NONE if no new data available + */ +extern zbar_symbol_type_t +zbar_decoder_get_type(const zbar_decoder_t *decoder); + +/** setup data handler callback. + * the registered function will be called by the decoder + * just before zbar_decode_width() returns a non-zero value. + * pass a NULL value to disable callbacks. + * @returns the previously registered handler + */ +extern zbar_decoder_handler_t* +zbar_decoder_set_handler(zbar_decoder_t *decoder, + zbar_decoder_handler_t *handler); + +/** associate user specified data value with the decoder. */ +extern void zbar_decoder_set_userdata(zbar_decoder_t *decoder, + void *userdata); + +/** return user specified data value associated with the decoder. */ +extern void *zbar_decoder_get_userdata(const zbar_decoder_t *decoder); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Scanner interface + * @anchor c-scanner + * low-level linear intensity sample stream scanner interface. + * identifies "bar" edges and measures width between them. + * optionally passes to bar width decoder + */ +/*@{*/ + +struct zbar_scanner_s; +/** opaque scanner object. */ +typedef struct zbar_scanner_s zbar_scanner_t; + +/** constructor. + * if decoder is non-NULL it will be attached to scanner + * and called automatically at each new edge + * current color is initialized to ::ZBAR_SPACE + * (so an initial BAR->SPACE transition may be discarded) + */ +extern zbar_scanner_t *zbar_scanner_create(zbar_decoder_t *decoder); + +/** destructor. */ +extern void zbar_scanner_destroy(zbar_scanner_t *scanner); + +/** clear all scanner state. + * also resets an associated decoder + */ +extern zbar_symbol_type_t zbar_scanner_reset(zbar_scanner_t *scanner); + +/** mark start of a new scan pass. resets color to ::ZBAR_SPACE. + * also updates an associated decoder. + * @returns any decode results flushed from the pipeline + * @note when not using callback handlers, the return value should + * be checked the same as zbar_scan_y() + * @note call zbar_scanner_flush() at least twice before calling this + * method to ensure no decode results are lost + */ +extern zbar_symbol_type_t zbar_scanner_new_scan(zbar_scanner_t *scanner); + +/** flush scanner processing pipeline. + * forces current scanner position to be a scan boundary. + * call multiple times (max 3) to completely flush decoder. + * @returns any decode/scan results flushed from the pipeline + * @note when not using callback handlers, the return value should + * be checked the same as zbar_scan_y() + * @since 0.9 + */ +extern zbar_symbol_type_t zbar_scanner_flush(zbar_scanner_t *scanner); + +/** process next sample intensity value. + * intensity (y) is in arbitrary relative units. + * @returns result of zbar_decode_width() if a decoder is attached, + * otherwise @returns (::ZBAR_PARTIAL) when new edge is detected + * or 0 (::ZBAR_NONE) if no new edge is detected + */ +extern zbar_symbol_type_t zbar_scan_y(zbar_scanner_t *scanner, + int y); + +/** process next sample from RGB (or BGR) triple. */ +static inline zbar_symbol_type_t zbar_scan_rgb24 (zbar_scanner_t *scanner, + unsigned char *rgb) +{ + return(zbar_scan_y(scanner, rgb[0] + rgb[1] + rgb[2])); +} + +/** retrieve last scanned width. */ +extern unsigned zbar_scanner_get_width(const zbar_scanner_t *scanner); + +/** retrieve sample position of last edge. + * @since 0.10 + */ +extern unsigned zbar_scanner_get_edge(const zbar_scanner_t *scn, + unsigned offset, + int prec); + +/** retrieve last scanned color. */ +extern zbar_color_t zbar_scanner_get_color(const zbar_scanner_t *scanner); + +/*@}*/ + +#ifdef __cplusplus + } +} + +# include "zbar/Exception.h" +# include "zbar/Decoder.h" +# include "zbar/Scanner.h" +# include "zbar/Symbol.h" +# include "zbar/Image.h" +# include "zbar/ImageScanner.h" +# include "zbar/Video.h" +# include "zbar/Window.h" +# include "zbar/Processor.h" +#endif + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Decoder.h b/Software/External_Repositories/ZBar/include/zbar/Decoder.h new file mode 100644 index 000000000..3e65d5ed5 --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Decoder.h @@ -0,0 +1,193 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_DECODER_H_ +#define _ZBAR_DECODER_H_ + +/// @file +/// Decoder C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Decoder.h" +#endif + +#include + +namespace zbar { + +/// low-level bar width stream decoder interface. +/// identifies symbols and extracts encoded data + +class Decoder { + public: + + /// Decoder result handler. + /// applications should subtype this and pass an instance to + /// set_handler() to implement result processing + class Handler { + public: + virtual ~Handler() { } + + /// invoked by the Decoder as decode results become available. + virtual void decode_callback(Decoder &decoder) = 0; + }; + + /// constructor. + Decoder () + : _handler(NULL) + { + _decoder = zbar_decoder_create(); + } + + ~Decoder () + { + zbar_decoder_destroy(_decoder); + } + + /// clear all decoder state. + /// see zbar_decoder_reset() + void reset () + { + zbar_decoder_reset(_decoder); + } + + /// mark start of a new scan pass. + /// see zbar_decoder_new_scan() + void new_scan () + { + zbar_decoder_new_scan(_decoder); + } + + /// process next bar/space width from input stream. + /// see zbar_decode_width() + zbar_symbol_type_t decode_width (unsigned width) + { + return(zbar_decode_width(_decoder, width)); + } + + /// process next bar/space width from input stream. + /// see zbar_decode_width() + Decoder& operator<< (unsigned width) + { + zbar_decode_width(_decoder, width); + return(*this); + } + + /// retrieve color of @em next element passed to Decoder. + /// see zbar_decoder_get_color() + zbar_color_t get_color () const + { + return(zbar_decoder_get_color(_decoder)); + } + + /// retrieve last decoded symbol type. + /// see zbar_decoder_get_type() + zbar_symbol_type_t get_type () const + { + return(zbar_decoder_get_type(_decoder)); + } + + /// retrieve string name of last decoded symbol type. + /// see zbar_get_symbol_name() + const char *get_symbol_name () const + { + return(zbar_get_symbol_name(zbar_decoder_get_type(_decoder))); + } + + /// retrieve string name for last decode addon. + /// see zbar_get_addon_name() + const char *get_addon_name () const + { + return(zbar_get_addon_name(zbar_decoder_get_type(_decoder))); + } + + /// retrieve last decoded data in ASCII format as a char array. + /// see zbar_decoder_get_data() + const char *get_data_chars() const + { + return(zbar_decoder_get_data(_decoder)); + } + + /// retrieve last decoded data as a std::string. + /// see zbar_decoder_get_data() + const std::string get_data_string() const + { + return(std::string(zbar_decoder_get_data(_decoder), + zbar_decoder_get_data_length(_decoder))); + } + + /// retrieve last decoded data as a std::string. + /// see zbar_decoder_get_data() + const std::string get_data() const + { + return(get_data_string()); + } + + /// retrieve length of decoded binary data. + /// see zbar_decoder_get_data_length() + int get_data_length() const + { + return(zbar_decoder_get_data_length(_decoder)); + } + + /// setup callback to handle result data. + void set_handler (Handler &handler) + { + _handler = &handler; + zbar_decoder_set_handler(_decoder, _cb); + zbar_decoder_set_userdata(_decoder, this); + } + + /// set config for indicated symbology (0 for all) to specified value. + /// @see zbar_decoder_set_config() + /// @since 0.4 + int set_config (zbar_symbol_type_t symbology, + zbar_config_t config, + int value) + { + return(zbar_decoder_set_config(_decoder, symbology, config, value)); + } + + /// set config parsed from configuration string. + /// @see zbar_decoder_parse_config() + /// @since 0.4 + int set_config (std::string cfgstr) + { + return(zbar_decoder_parse_config(_decoder, cfgstr.c_str())); + } + + private: + friend class Scanner; + zbar_decoder_t *_decoder; + Handler *_handler; + + static void _cb (zbar_decoder_t *cdcode) + { + Decoder *dcode = (Decoder*)zbar_decoder_get_userdata(cdcode); + if(dcode && dcode->_handler) + dcode->_handler->decode_callback(*dcode); + } +}; + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Exception.h b/Software/External_Repositories/ZBar/include/zbar/Exception.h new file mode 100644 index 000000000..236622f69 --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Exception.h @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_EXCEPTION_H_ +#define _ZBAR_EXCEPTION_H_ + +/// @file +/// C++ Exception definitions + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Exception.h" +#endif + +#include +#include + +namespace zbar { + +/// base class for exceptions defined by this API. +class Exception : public std::exception { + +public: + /// create exception from C library error + Exception (const void *obj = NULL) + : std::exception(), + _obj(obj) + { } + + ~Exception () throw() { } + + /// retrieve error message + virtual const char* what () const throw() + { + if(!_obj) + return("zbar library unspecified generic error"); + return(_zbar_error_string(_obj, 0)); + } + +private: + const void *_obj; +}; + +/// internal library error. +class InternalError : public Exception { +public: + /// create exception from C library error + InternalError (const void *obj) + : Exception(obj) + { } +}; + +/// unsupported request. +class UnsupportedError : public Exception { +public: + /// create exception from C library error + UnsupportedError (const void *obj) + : Exception(obj) + { } +}; + +/// invalid request. +class InvalidError : public Exception { +public: + /// create exception from C library error + InvalidError (const void *obj) + : Exception(obj) + { } +}; + +/// failed system call. +class SystemError : public Exception { +public: + /// create exception from C library error + SystemError (const void *obj) + : Exception(obj) + { } +}; + +/// locking error. +class LockingError : public Exception { +public: + /// create exception from C library error + LockingError (const void *obj) + : Exception(obj) + { } +}; + +/// all resources busy. +class BusyError : public Exception { +public: + /// create exception from C library error + BusyError (const void *obj) + : Exception(obj) + { } +}; + +/// X11 display error. +class XDisplayError : public Exception { +public: + /// create exception from C library error + XDisplayError (const void *obj) + : Exception(obj) + { } +}; + +/// X11 protocol error. +class XProtoError : public Exception { +public: + /// create exception from C library error + XProtoError (const void *obj) + : Exception(obj) + { } +}; + +/// output window is closed. +class ClosedError : public Exception { +public: + /// create exception from C library error + ClosedError (const void *obj) + : Exception(obj) + { } +}; + +/// image format error +class FormatError : public Exception { + // FIXME needs c equivalent + + virtual const char* what () const throw() + { + // FIXME what format? + return("unsupported format"); + } +}; + +/// @internal + +/// extract error information and create exception. +static inline std::exception throw_exception (const void *obj) +{ + switch(_zbar_get_error_code(obj)) { + case ZBAR_ERR_NOMEM: + throw std::bad_alloc(); + case ZBAR_ERR_INTERNAL: + throw InternalError(obj); + case ZBAR_ERR_UNSUPPORTED: + throw UnsupportedError(obj); + case ZBAR_ERR_INVALID: + throw InvalidError(obj); + case ZBAR_ERR_SYSTEM: + throw SystemError(obj); + case ZBAR_ERR_LOCKING: + throw LockingError(obj); + case ZBAR_ERR_BUSY: + throw BusyError(obj); + case ZBAR_ERR_XDISPLAY: + throw XDisplayError(obj); + case ZBAR_ERR_XPROTO: + throw XProtoError(obj); + case ZBAR_ERR_CLOSED: + throw ClosedError(obj); + default: + throw Exception(obj); + } +} + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Image.h b/Software/External_Repositories/ZBar/include/zbar/Image.h new file mode 100644 index 000000000..e1d964404 --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Image.h @@ -0,0 +1,290 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_IMAGE_H_ +#define _ZBAR_IMAGE_H_ + +/// @file +/// Image C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Image.h" +#endif + +#include +#include +#include "Symbol.h" +#include "Exception.h" + +namespace zbar { + +class Video; + +/// stores image data samples along with associated format and size +/// metadata + +class Image { +public: + + /// general Image result handler. + /// applications should subtype this and pass an instance to + /// eg. ImageScanner::set_handler() to implement result processing + class Handler { + public: + virtual ~Handler() { } + + /// invoked by library when Image should be processed + virtual void image_callback(Image &image) = 0; + + /// cast this handler to the C handler + operator zbar_image_data_handler_t* () const + { + return(_cb); + } + + private: + static void _cb (zbar_image_t *zimg, + const void *userdata) + { + if(userdata) { + Image *image = (Image*)zbar_image_get_userdata(zimg); + ((Handler*)userdata)->image_callback(*image); + } + } + }; + + class SymbolIterator : public zbar::SymbolIterator { + public: + /// default constructor. + SymbolIterator () + : zbar::SymbolIterator() + { } + + /// constructor. + SymbolIterator (const SymbolSet &syms) + : zbar::SymbolIterator(syms) + { } + + /// copy constructor. + SymbolIterator (const SymbolIterator& iter) + : zbar::SymbolIterator(iter) + { } + }; + + /// constructor. + /// create a new Image with the specified parameters + Image (unsigned width = 0, + unsigned height = 0, + const std::string& format = "", + const void *data = NULL, + unsigned long length = 0) + : _img(zbar_image_create()) + { + zbar_image_set_userdata(_img, this); + if(width && height) + set_size(width, height); + if(format.length()) + set_format(format); + if(data && length) + set_data(data, length); + } + + ~Image () + { + zbar_image_ref(_img, -1); + } + + /// cast to C image object + operator const zbar_image_t* () const + { + return(_img); + } + + /// cast to C image object + operator zbar_image_t* () + { + return(_img); + } + + /// retrieve the image format. + /// see zbar_image_get_format() + unsigned long get_format () const + { + return(zbar_image_get_format(_img)); + } + + /// specify the fourcc image format code for image sample data. + /// see zbar_image_set_format() + void set_format (unsigned long format) + { + zbar_image_set_format(_img, format); + } + + /// specify the fourcc image format code for image sample data. + /// see zbar_image_set_format() + void set_format (const std::string& format) + { + if(format.length() != 4) + throw FormatError(); + unsigned long fourcc = ((format[0] & 0xff) | + ((format[1] & 0xff) << 8) | + ((format[2] & 0xff) << 16) | + ((format[3] & 0xff) << 24)); + zbar_image_set_format(_img, fourcc); + } + + /// retrieve a "sequence" (page/frame) number associated with this + /// image. + /// see zbar_image_get_sequence() + /// @since 0.6 + unsigned get_sequence () const + { + return(zbar_image_get_sequence(_img)); + } + + /// associate a "sequence" (page/frame) number with this image. + /// see zbar_image_set_sequence() + /// @since 0.6 + void set_sequence (unsigned sequence_num) + { + zbar_image_set_sequence(_img, sequence_num); + } + + /// retrieve the width of the image. + /// see zbar_image_get_width() + unsigned get_width () const + { + return(zbar_image_get_width(_img)); + } + + /// retrieve the height of the image. + /// see zbar_image_get_height() + unsigned get_height () const + { + return(zbar_image_get_height(_img)); + } + + /// specify the pixel size of the image. + /// see zbar_image_set_size() + void set_size (unsigned width, + unsigned height) + { + zbar_image_set_size(_img, width, height); + } + + /// return the image sample data. + /// see zbar_image_get_data() + const void *get_data () const + { + return(zbar_image_get_data(_img)); + } + + /// return the size of the image sample data. + /// see zbar_image_get_data_length() + /// @since 0.6 + unsigned long get_data_length () const + { + return(zbar_image_get_data_length(_img)); + } + + /// specify image sample data. + /// see zbar_image_set_data() + void set_data (const void *data, + unsigned long length) + { + zbar_image_set_data(_img, data, length, _cleanup); + } + + /// image format conversion. + /// see zbar_image_convert() + Image convert (unsigned long format) const + { + zbar_image_t *img = zbar_image_convert(_img, format); + if(img) + return(Image(img)); + throw FormatError(); + } + + /// image format conversion with crop/pad. + /// see zbar_image_convert_resize() + /// @since 0.4 + Image convert (unsigned long format, + unsigned width, + unsigned height) const + { + zbar_image_t *img = + zbar_image_convert_resize(_img, format, width, height); + if(img) + return(Image(img)); + throw FormatError(); + } + + const SymbolSet get_symbols () const { + return(SymbolSet(zbar_image_get_symbols(_img))); + } + + void set_symbols (const SymbolSet &syms) { + zbar_image_set_symbols(_img, syms); + } + + /// create a new SymbolIterator over decoded results. + SymbolIterator symbol_begin () const { + return(SymbolIterator(get_symbols())); + } + + /// return a SymbolIterator suitable for ending iteration. + SymbolIterator symbol_end () const { + return(SymbolIterator()); + } + +protected: + + friend class Video; + + /// constructor. + /// @internal + /// create a new Image from a zbar_image_t C object + Image (zbar_image_t *src, + int refs = 0) + : _img(src) + { + if(refs) + zbar_image_ref(_img, refs); + zbar_image_set_userdata(_img, this); + } + + /// default data cleanup (noop) + /// @internal + static void _cleanup (zbar_image_t *img) + { + // by default nothing is cleaned + assert(img); + assert(zbar_image_get_userdata(img)); + } + +private: + zbar_image_t *_img; +}; + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/ImageScanner.h b/Software/External_Repositories/ZBar/include/zbar/ImageScanner.h new file mode 100644 index 000000000..bda84338d --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/ImageScanner.h @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_IMAGE_SCANNER_H_ +#define _ZBAR_IMAGE_SCANNER_H_ + +/// @file +/// Image Scanner C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/ImageScanner.h" +#endif + +#include "Image.h" + +namespace zbar { + +/// mid-level image scanner interface. +/// reads barcodes from a 2-D Image + +class ImageScanner { +public: + /// constructor. + ImageScanner (zbar_image_scanner_t *scanner = NULL) + { + if(scanner) + _scanner = scanner; + else + _scanner = zbar_image_scanner_create(); + } + + ~ImageScanner () + { + zbar_image_scanner_destroy(_scanner); + } + + /// cast to C image_scanner object + operator zbar_image_scanner_t* () const + { + return(_scanner); + } + + /// setup result handler callback. + void set_handler (Image::Handler &handler) + { + zbar_image_scanner_set_data_handler(_scanner, handler, &handler); + } + + /// set config for indicated symbology (0 for all) to specified value. + /// @see zbar_image_scanner_set_config() + /// @since 0.4 + int set_config (zbar_symbol_type_t symbology, + zbar_config_t config, + int value) + { + return(zbar_image_scanner_set_config(_scanner, symbology, + config, value)); + } + + /// set config parsed from configuration string. + /// @see zbar_image_scanner_parse_config() + /// @since 0.4 + int set_config (std::string cfgstr) + { + return(zbar_image_scanner_parse_config(_scanner, cfgstr.c_str())); + } + + /// enable or disable the inter-image result cache. + /// see zbar_image_scanner_enable_cache() + void enable_cache (bool enable = true) + { + zbar_image_scanner_enable_cache(_scanner, enable); + } + + /// remove previous results from scanner and image. + /// @see zbar_image_scanner_recycle_image() + /// @since 0.10 + void recycle_image (Image &image) + { + zbar_image_scanner_recycle_image(_scanner, image); + } + + /// retrieve decode results for last scanned image. + /// @see zbar_image_scanner_get_results() + /// @since 0.10 + const SymbolSet get_results () const { + return(SymbolSet(zbar_image_scanner_get_results(_scanner))); + } + + /// scan for symbols in provided image. + /// see zbar_scan_image() + int scan (Image& image) + { + return(zbar_scan_image(_scanner, image)); + } + + /// scan for symbols in provided image. + /// see zbar_scan_image() + ImageScanner& operator<< (Image& image) + { + scan(image); + return(*this); + } + +private: + zbar_image_scanner_t *_scanner; +}; + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Processor.h b/Software/External_Repositories/ZBar/include/zbar/Processor.h new file mode 100644 index 000000000..51b0e1c4a --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Processor.h @@ -0,0 +1,223 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_PROCESSOR_H_ +#define _ZBAR_PROCESSOR_H_ + +/// @file +/// Processor C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Processor.h" +#endif + +#include "Exception.h" +#include "Image.h" + +namespace zbar { + +/// high-level self-contained image processor. +/// processes video and images for barcodes, optionally displaying +/// images to a library owned output window + +class Processor { + public: + /// value to pass for no timeout. + static const int FOREVER = -1; + + /// constructor. + Processor (bool threaded = true, + const char *video_device = "", + bool enable_display = true) + { + _processor = zbar_processor_create(threaded); + if(!_processor) + throw std::bad_alloc(); + init(video_device, enable_display); + } + + ~Processor () + { + zbar_processor_destroy(_processor); + } + + /// cast to C processor object. + operator zbar_processor_t* () + { + return(_processor); + } + + /// opens a video input device and/or prepares to display output. + /// see zbar_processor_init() + void init (const char *video_device = "", + bool enable_display = true) + { + if(zbar_processor_init(_processor, video_device, enable_display)) + throw_exception(_processor); + } + + /// setup result handler callback. + /// see zbar_processor_set_data_handler() + void set_handler (Image::Handler& handler) + { + zbar_processor_set_data_handler(_processor, handler, &handler); + } + + /// set config for indicated symbology (0 for all) to specified value. + /// @see zbar_processor_set_config() + /// @since 0.4 + int set_config (zbar_symbol_type_t symbology, + zbar_config_t config, + int value) + { + return(zbar_processor_set_config(_processor, symbology, + config, value)); + } + + /// set config parsed from configuration string. + /// @see zbar_processor_parse_config() + /// @since 0.4 + int set_config (std::string cfgstr) + { + return(zbar_processor_parse_config(_processor, cfgstr.c_str())); + } + + /// retrieve the current state of the ouput window. + /// see zbar_processor_is_visible() + bool is_visible () + { + int rc = zbar_processor_is_visible(_processor); + if(rc < 0) + throw_exception(_processor); + return(rc != 0); + } + + /// show or hide the display window owned by the library. + /// see zbar_processor_set_visible() + void set_visible (bool visible = true) + { + if(zbar_processor_set_visible(_processor, visible) < 0) + throw_exception(_processor); + } + + /// control the processor in free running video mode. + /// see zbar_processor_set_active() + void set_active (bool active = true) + { + if(zbar_processor_set_active(_processor, active) < 0) + throw_exception(_processor); + } + + /// retrieve decode results for last scanned image. + /// @see zbar_processor_get_results() + /// @since 0.10 + const SymbolSet get_results () const { + return(SymbolSet(zbar_processor_get_results(_processor))); + } + + /// wait for input to the display window from the user. + /// see zbar_processor_user_wait() + int user_wait (int timeout = FOREVER) + { + int rc = zbar_processor_user_wait(_processor, timeout); + if(rc < 0) + throw_exception(_processor); + return(rc); + } + + /// process from the video stream until a result is available. + /// see zbar_process_one() + void process_one (int timeout = FOREVER) + { + if(zbar_process_one(_processor, timeout) < 0) + throw_exception(_processor); + } + + /// process the provided image for barcodes. + /// see zbar_process_image() + void process_image (Image& image) + { + if(zbar_process_image(_processor, image) < 0) + throw_exception(_processor); + } + + /// process the provided image for barcodes. + /// see zbar_process_image() + Processor& operator<< (Image& image) + { + process_image(image); + return(*this); + } + + /// force specific input and output formats for debug/testing. + /// see zbar_processor_force_format() + void force_format (unsigned long input_format, + unsigned long output_format) + { + if(zbar_processor_force_format(_processor, input_format, + output_format)) + throw_exception(_processor); + } + + /// force specific input and output formats for debug/testing. + /// see zbar_processor_force_format() + void force_format (std::string& input_format, + std::string& output_format) + { + unsigned long ifourcc = *(unsigned long*)input_format.c_str(); + unsigned long ofourcc = *(unsigned long*)output_format.c_str(); + if(zbar_processor_force_format(_processor, ifourcc, ofourcc)) + throw_exception(_processor); + } + + /// request a preferred size for the video image from the device. + /// see zbar_processor_request_size() + /// @since 0.6 + void request_size (int width, int height) + { + zbar_processor_request_size(_processor, width, height); + } + + /// request a preferred driver interface version for debug/testing. + /// see zbar_processor_request_interface() + /// @since 0.6 + void request_interface (int version) + { + zbar_processor_request_interface(_processor, version); + } + + /// request a preferred I/O mode for debug/testing. + /// see zbar_processor_request_iomode() + /// @since 0.7 + void request_iomode (int iomode) + { + if(zbar_processor_request_iomode(_processor, iomode)) + throw_exception(_processor); + } + + private: + zbar_processor_t *_processor; +}; + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Scanner.h b/Software/External_Repositories/ZBar/include/zbar/Scanner.h new file mode 100644 index 000000000..8c9a756ba --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Scanner.h @@ -0,0 +1,162 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_SCANNER_H_ +#define _ZBAR_SCANNER_H_ + +/// @file +/// Scanner C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Scanner.h" +#endif + +#include + +namespace zbar { + +/// low-level linear intensity sample stream scanner interface. +/// identifies "bar" edges and measures width between them. +/// optionally passes to bar width Decoder + +class Scanner { + public: + + /// constructor. + /// @param decoder reference to a Decoder instance which will + /// be passed scan results automatically + Scanner (Decoder& decoder) + { + _scanner = zbar_scanner_create(decoder._decoder); + } + + /// constructor. + /// @param decoder pointer to a Decoder instance which will + /// be passed scan results automatically + Scanner (Decoder* decoder = NULL) + { + zbar_decoder_t *zdcode = NULL; + if(decoder) + zdcode = decoder->_decoder; + _scanner = zbar_scanner_create(zdcode); + } + + ~Scanner () + { + zbar_scanner_destroy(_scanner); + } + + /// clear all scanner state. + /// see zbar_scanner_reset() + void reset () + { + zbar_scanner_reset(_scanner); + } + + /// mark start of a new scan pass. + /// see zbar_scanner_new_scan() + zbar_symbol_type_t new_scan () + { + _type = zbar_scanner_new_scan(_scanner); + return(_type); + } + + /// flush scanner pipeline. + /// see zbar_scanner_flush() + zbar_symbol_type_t flush () + { + _type = zbar_scanner_flush(_scanner); + return(_type); + } + + /// process next sample intensity value. + /// see zbar_scan_y() + zbar_symbol_type_t scan_y (int y) + { + _type = zbar_scan_y(_scanner, y); + return(_type); + } + + /// process next sample intensity value. + /// see zbar_scan_y() + Scanner& operator<< (int y) + { + _type = zbar_scan_y(_scanner, y); + return(*this); + } + + /// process next sample from RGB (or BGR) triple. + /// see zbar_scan_rgb24() + zbar_symbol_type_t scan_rgb24 (unsigned char *rgb) + { + _type = zbar_scan_rgb24(_scanner, rgb); + return(_type); + } + + /// process next sample from RGB (or BGR) triple. + /// see zbar_scan_rgb24() + Scanner& operator<< (unsigned char *rgb) + { + _type = zbar_scan_rgb24(_scanner, rgb); + return(*this); + } + + /// retrieve last scanned width. + /// see zbar_scanner_get_width() + unsigned get_width () const + { + return(zbar_scanner_get_width(_scanner)); + } + + /// retrieve last scanned color. + /// see zbar_scanner_get_color() + zbar_color_t get_color () const + { + return(zbar_scanner_get_color(_scanner)); + } + + /// retrieve last scan result. + zbar_symbol_type_t get_type () const + { + return(_type); + } + + /// cast to C scanner + operator zbar_scanner_t* () const + { + return(_scanner); + } + + /// retrieve C scanner + const zbar_scanner_t *get_c_scanner () const + { + return(_scanner); + } + + private: + zbar_scanner_t *_scanner; + zbar_symbol_type_t _type; +}; + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Symbol.h b/Software/External_Repositories/ZBar/include/zbar/Symbol.h new file mode 100644 index 000000000..b47732a2c --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Symbol.h @@ -0,0 +1,451 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_SYMBOL_H_ +#define _ZBAR_SYMBOL_H_ + +/// @file +/// Symbol C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Symbol.h" +#endif + +#include +#include +#include +#include + +namespace zbar { + +class SymbolIterator; + +/// container for decoded result symbols associated with an image +/// or a composite symbol. + +class SymbolSet { +public: + /// constructor. + SymbolSet (const zbar_symbol_set_t *syms = NULL) + : _syms(syms) + { + ref(); + } + + /// copy constructor. + SymbolSet (const SymbolSet& syms) + : _syms(syms._syms) + { + ref(); + } + + /// destructor. + ~SymbolSet () + { + ref(-1); + } + + /// manipulate reference count. + void ref (int delta = 1) const + { + if(_syms) + zbar_symbol_set_ref((zbar_symbol_set_t*)_syms, delta); + } + + /// cast to C symbol set. + operator const zbar_symbol_set_t* () const + { + return(_syms); + } + + int get_size () + { + return((_syms) ? zbar_symbol_set_get_size(_syms) : 0); + } + + /// create a new SymbolIterator over decoded results. + SymbolIterator symbol_begin() const; + + /// return a SymbolIterator suitable for ending iteration. + const SymbolIterator symbol_end() const; + +private: + const zbar_symbol_set_t *_syms; +}; + +/// decoded barcode symbol result object. stores type, data, and +/// image location of decoded symbol + +class Symbol { +public: + + /// image pixel location (x, y) coordinate tuple. + class Point { + public: + int x; ///< x-coordinate. + int y; ///< y-coordinate. + + Point () { } + + Point(int x, int y) + : x(x), y(y) + { } + + /// copy constructor. + Point (const Point& pt) + { + x = pt.x; + y = pt.y; + } + }; + + /// iteration over Point objects in a symbol location polygon. + class PointIterator + : public std::iterator { + + public: + /// constructor. + PointIterator (const Symbol *sym = NULL, + int index = 0) + : _sym(sym), + _index(index) + { + sym->ref(1); + if(!sym || + (unsigned)_index >= zbar_symbol_get_loc_size(*_sym)) + _index = -1; + } + + /// constructor. + PointIterator (const PointIterator& iter) + : _sym(iter._sym), + _index(iter._index) + { + _sym->ref(); + } + + /// destructor. + ~PointIterator () + { + _sym->ref(-1); + } + + /// advance iterator to next Point. + PointIterator& operator++ () + { + unsigned int i = ++_index; + if(i >= zbar_symbol_get_loc_size(*_sym)) + _index = -1; + return(*this); + } + + /// retrieve currently referenced Point. + const Point operator* () const + { + assert(_index >= 0); + return(Point(zbar_symbol_get_loc_x(*_sym, _index), + zbar_symbol_get_loc_y(*_sym, _index))); + } + + /// test if two iterators refer to the same Point in the same + /// Symbol. + bool operator== (const PointIterator& iter) const + { + return(_index == iter._index && + ((_index < 0) || _sym == iter._sym)); + } + + /// test if two iterators refer to the same Point in the same + /// Symbol. + bool operator!= (const PointIterator& iter) const + { + return(!(*this == iter)); + } + + private: + const Symbol *_sym; + int _index; + }; + + /// constructor. + Symbol (const zbar_symbol_t *sym = NULL) + : _xmlbuf(NULL), + _xmllen(0) + { + init(sym); + ref(); + } + + /// copy constructor. + Symbol (const Symbol& sym) + : _sym(sym._sym), + _type(sym._type), + _data(sym._data), + _xmlbuf(NULL), + _xmllen(0) + { + ref(); + } + + /// destructor. + ~Symbol () { + if(_xmlbuf) + free(_xmlbuf); + ref(-1); + } + + void ref (int delta = 1) const + { + if(_sym) + zbar_symbol_ref((zbar_symbol_t*)_sym, delta); + } + + /// cast to C symbol. + operator const zbar_symbol_t* () const + { + return(_sym); + } + + /// test if two Symbol objects refer to the same C symbol. + bool operator== (const Symbol& sym) const + { + return(_sym == sym._sym); + } + + /// test if two Symbol objects refer to the same C symbol. + bool operator!= (const Symbol& sym) const + { + return(!(*this == sym)); + } + + /// retrieve type of decoded symbol. + zbar_symbol_type_t get_type () const + { + return(_type); + } + + /// retrieve the string name of the symbol type. + const std::string get_type_name () const + { + return(zbar_get_symbol_name(_type)); + } + + /// retrieve the string name for any addon. + const std::string get_addon_name () const + { + return(zbar_get_addon_name(_type)); + } + + /// retrieve data decoded from symbol. + const std::string get_data () const + { + return(_data); + } + + /// retrieve length of binary data + unsigned get_data_length () const + { + return((_sym) ? zbar_symbol_get_data_length(_sym) : 0); + } + + /// retrieve inter-frame coherency count. + /// see zbar_symbol_get_count() + /// @since 1.5 + int get_count () const + { + return((_sym) ? zbar_symbol_get_count(_sym) : -1); + } + + SymbolSet get_components () const + { + return(SymbolSet((_sym) ? zbar_symbol_get_components(_sym) : NULL)); + } + + /// create a new PointIterator at the start of the location + /// polygon. + PointIterator point_begin() const + { + return(PointIterator(this)); + } + + /// return a PointIterator suitable for ending iteration. + const PointIterator point_end() const + { + return(PointIterator()); + } + + /// see zbar_symbol_get_loc_size(). + int get_location_size () const + { + return((_sym) ? zbar_symbol_get_loc_size(_sym) : 0); + } + + /// see zbar_symbol_get_loc_x(). + int get_location_x (unsigned index) const + { + return((_sym) ? zbar_symbol_get_loc_x(_sym, index) : -1); + } + + /// see zbar_symbol_get_loc_y(). + int get_location_y (unsigned index) const + { + return((_sym) ? zbar_symbol_get_loc_y(_sym, index) : -1); + } + + /// see zbar_symbol_xml(). + const std::string xml () const + { + if(!_sym) + return(""); + return(zbar_symbol_xml(_sym, (char**)&_xmlbuf, (unsigned*)&_xmllen)); + } + +protected: + + friend class SymbolIterator; + + /// (re)initialize Symbol from C symbol object. + void init (const zbar_symbol_t *sym = NULL) + { + _sym = sym; + if(sym) { + _type = zbar_symbol_get_type(sym); + _data = std::string(zbar_symbol_get_data(sym), + zbar_symbol_get_data_length(sym)); + } + else { + _type = ZBAR_NONE; + _data = ""; + } + } + +private: + const zbar_symbol_t *_sym; + zbar_symbol_type_t _type; + std::string _data; + char *_xmlbuf; + unsigned _xmllen; +}; + +/// iteration over Symbol result objects in a scanned Image or SymbolSet. +class SymbolIterator + : public std::iterator { + +public: + /// default constructor. + SymbolIterator () + { } + + /// constructor. + SymbolIterator (const SymbolSet &syms) + : _syms(syms) + { + const zbar_symbol_set_t *zsyms = _syms; + if(zsyms) + _sym.init(zbar_symbol_set_first_symbol(zsyms)); + } + + /// copy constructor. + SymbolIterator (const SymbolIterator& iter) + : _syms(iter._syms) + { + const zbar_symbol_set_t *zsyms = _syms; + if(zsyms) + _sym.init(zbar_symbol_set_first_symbol(zsyms)); + } + + ~SymbolIterator () + { + _sym.init(); + } + + /// advance iterator to next Symbol. + SymbolIterator& operator++ () + { + const zbar_symbol_t *zsym = _sym; + if(zsym) + _sym.init(zbar_symbol_next(zsym)); + else { + const zbar_symbol_set_t *zsyms = _syms; + if(zsyms) + _sym.init(zbar_symbol_set_first_symbol(zsyms)); + } + return(*this); + } + + /// retrieve currently referenced Symbol. + const Symbol operator* () const + { + return(_sym); + } + + /// access currently referenced Symbol. + const Symbol* operator-> () const + { + return(&_sym); + } + + /// test if two iterators refer to the same Symbol + bool operator== (const SymbolIterator& iter) const + { + // it is enough to test the symbols, as they belong + // to only one set (also simplifies invalid case) + return(_sym == iter._sym); + } + + /// test if two iterators refer to the same Symbol + bool operator!= (const SymbolIterator& iter) const + { + return(!(*this == iter)); + } + + const SymbolIterator end () const { + return(SymbolIterator()); + } + +private: + SymbolSet _syms; + Symbol _sym; +}; + +inline SymbolIterator SymbolSet::symbol_begin () const { + return(SymbolIterator(*this)); +} + +inline const SymbolIterator SymbolSet::symbol_end () const { + return(SymbolIterator()); +} + +/// @relates Symbol +/// stream the string representation of a Symbol. +static inline std::ostream& operator<< (std::ostream& out, + const Symbol& sym) +{ + out << sym.get_type_name() + << sym.get_addon_name() + << ":" << sym.get_data(); + return(out); +} + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Video.h b/Software/External_Repositories/ZBar/include/zbar/Video.h new file mode 100644 index 000000000..2787d86b2 --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Video.h @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_VIDEO_H_ +#define _ZBAR_VIDEO_H_ + +/// @file +/// Video Input C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Video.h" +#endif + +#include "Image.h" + +namespace zbar { + +/// mid-level video source abstraction. +/// captures images from a video device + +class Video { +public: + /// constructor. + Video (zbar_video_t *video = NULL) + { + if(video) + _video = video; + else + _video = zbar_video_create(); + } + + /// constructor. + Video (std::string& device) + { + _video = zbar_video_create(); + open(device); + } + + ~Video () + { + zbar_video_destroy(_video); + } + + /// cast to C video object. + operator zbar_video_t* () const + { + return(_video); + } + + /// open and probe a video device. + void open (std::string& device) + { + if(zbar_video_open(_video, device.c_str())) + throw_exception(_video); + } + + /// close video device if open. + void close () + { + if(zbar_video_open(_video, NULL)) + throw_exception(_video); + } + + /// initialize video using a specific format for debug. + /// see zbar_video_init() + void init (unsigned long fourcc) + { + if(zbar_video_init(_video, fourcc)) + throw_exception(_video); + } + + /// initialize video using a specific format for debug. + /// see zbar_video_init() + void init (std::string& format) + { + unsigned int fourcc = *(unsigned int*)format.c_str(); + if(zbar_video_init(_video, fourcc)) + throw_exception(_video); + } + + /// retrieve file descriptor associated with open *nix video device. + /// see zbar_video_get_fd() + int get_fd () + { + return(zbar_video_get_fd(_video)); + } + + /// retrieve current output image width. + /// see zbar_video_get_width() + int get_width () + { + return(zbar_video_get_width(_video)); + } + + /// retrieve current output image height. + /// see zbar_video_get_height() + int get_height () + { + return(zbar_video_get_height(_video)); + } + + /// start/stop video capture. + /// see zbar_video_enable() + void enable (bool enable = true) + { + if(zbar_video_enable(_video, enable)) + throw_exception(_video); + } + + /// retrieve next captured image. + /// see zbar_video_next_image() + Image next_image () + { + zbar_image_t *img = zbar_video_next_image(_video); + if(!img) + throw_exception(_video); + return(Image(img)); + } + + /// request a preferred size for the video image from the device. + /// see zbar_video_request_size() + /// @since 0.6 + void request_size (int width, int height) + { + zbar_video_request_size(_video, width, height); + } + + /// request a preferred driver interface version for debug/testing. + /// see zbar_video_request_interface() + /// @since 0.6 + void request_interface (int version) + { + zbar_video_request_interface(_video, version); + } + + /// request a preferred I/O mode for debug/testing. + /// see zbar_video_request_iomode() + /// @since 0.7 + void request_iomode (int iomode) + { + if(zbar_video_request_iomode(_video, iomode)) + throw_exception(_video); + } + +private: + zbar_video_t *_video; +}; + +} + +#endif diff --git a/Software/External_Repositories/ZBar/include/zbar/Window.h b/Software/External_Repositories/ZBar/include/zbar/Window.h new file mode 100644 index 000000000..c91a405ae --- /dev/null +++ b/Software/External_Repositories/ZBar/include/zbar/Window.h @@ -0,0 +1,136 @@ +//------------------------------------------------------------------------ +// Copyright 2007-2009 (c) Jeff Brown +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ +#ifndef _ZBAR_WINDOW_H_ +#define _ZBAR_WINDOW_H_ + +/// @file +/// Output Window C++ wrapper + +#ifndef _ZBAR_H_ +# error "include zbar.h in your application, **not** zbar/Window.h" +#endif + +#include "Image.h" + +namespace zbar { + +/// mid-level output window abstraction. +/// displays images to user-specified platform specific output window + +class Window { +public: + /// constructor. + Window (zbar_window_t *window = NULL) + { + if(window) + _window = window; + else + _window = zbar_window_create(); + } + + /// constructor. + Window (void *x11_display_w32_hwnd, + unsigned long x11_drawable) + { + _window = zbar_window_create(); + attach(x11_display_w32_hwnd, x11_drawable); + } + + ~Window () + { + zbar_window_destroy(_window); + } + + /// cast to C window object. + operator zbar_window_t* () const + { + return(_window); + } + + /// associate reader with an existing platform window. + /// see zbar_window_attach() + void attach (void *x11_display_w32_hwnd, + unsigned long x11_drawable = 0) + { + if(zbar_window_attach(_window, + x11_display_w32_hwnd, x11_drawable) < 0) + throw_exception(_window); + } + + /// control content level of the reader overlay. + /// see zbar_window_set_overlay() + void set_overlay (int level) + { + zbar_window_set_overlay(_window, level); + } + + /// retrieve current content level of reader overlay. + /// see zbar_window_get_overlay() + + /// draw a new image into the output window. + /// see zbar_window_draw() + void draw (Image& image) + { + if(zbar_window_draw(_window, image) < 0) + throw_exception(_window); + } + + /// clear the image from the output window. + /// see zbar_window_draw() + void clear () + { + if(zbar_window_draw(_window, NULL) < 0) + throw_exception(_window); + } + + /// redraw the last image. + /// zbar_window_redraw() + void redraw () + { + if(zbar_window_redraw(_window) < 0) + throw_exception(_window); + } + + /// resize the image window. + /// zbar_window_resize() + void resize (unsigned width, unsigned height) + { + if(zbar_window_resize(_window, width, height) < 0) + throw_exception(_window); + } + +private: + zbar_window_t *_window; +}; + +/// select a compatible format between video input and output window. +/// see zbar_negotiate_format() +static inline void negotiate_format (Video& video, Window& window) +{ + if(zbar_negotiate_format(video, window) < 0) + throw_exception(video); +} + +} + +#endif diff --git a/Software/External_Repositories/ZBar/lib/libzbar-0.def b/Software/External_Repositories/ZBar/lib/libzbar-0.def new file mode 100644 index 000000000..6c0d54b3d --- /dev/null +++ b/Software/External_Repositories/ZBar/lib/libzbar-0.def @@ -0,0 +1,120 @@ +EXPORTS +_zbar_error_spew +_zbar_error_string +_zbar_get_error_code +zbar_decode_width +zbar_decoder_create +zbar_decoder_destroy +zbar_decoder_get_color +zbar_decoder_get_data +zbar_decoder_get_data_length +zbar_decoder_get_type +zbar_decoder_get_userdata +zbar_decoder_new_scan +zbar_decoder_reset +zbar_decoder_set_config +zbar_decoder_set_handler +zbar_decoder_set_userdata +zbar_get_addon_name +zbar_get_symbol_name +zbar_image_convert +zbar_image_convert_resize +zbar_image_copy +zbar_image_create +zbar_image_destroy +zbar_image_first_symbol +zbar_image_free_data +zbar_image_get_data +zbar_image_get_data_length +zbar_image_get_format +zbar_image_get_height +zbar_image_get_sequence +zbar_image_get_symbols +zbar_image_get_userdata +zbar_image_get_width +zbar_image_ref +zbar_image_scanner_create +zbar_image_scanner_destroy +zbar_image_scanner_enable_cache +zbar_image_scanner_get_results +zbar_image_scanner_recycle_image +zbar_image_scanner_set_config +zbar_image_scanner_set_data_handler +zbar_image_set_data +zbar_image_set_format +zbar_image_set_sequence +zbar_image_set_size +zbar_image_set_symbols +zbar_image_set_userdata +zbar_image_write +zbar_increase_verbosity +zbar_negotiate_format +zbar_parse_config +zbar_process_image +zbar_process_one +zbar_processor_create +zbar_processor_destroy +zbar_processor_force_format +zbar_processor_get_results +zbar_processor_get_userdata +zbar_processor_init +zbar_processor_is_visible +zbar_processor_request_interface +zbar_processor_request_iomode +zbar_processor_request_size +zbar_processor_set_active +zbar_processor_set_config +zbar_processor_set_data_handler +zbar_processor_set_userdata +zbar_processor_set_visible +zbar_processor_user_wait +zbar_scan_image +zbar_scan_y +zbar_scanner_create +zbar_scanner_destroy +zbar_scanner_flush +zbar_scanner_get_color +zbar_scanner_get_edge +zbar_scanner_get_state +zbar_scanner_get_width +zbar_scanner_new_scan +zbar_scanner_reset +zbar_set_verbosity +zbar_symbol_first_component +zbar_symbol_get_components +zbar_symbol_get_count +zbar_symbol_get_data +zbar_symbol_get_data_length +zbar_symbol_get_loc_size +zbar_symbol_get_loc_x +zbar_symbol_get_loc_y +zbar_symbol_get_quality +zbar_symbol_get_type +zbar_symbol_next +zbar_symbol_ref +zbar_symbol_set_first_symbol +zbar_symbol_set_get_size +zbar_symbol_set_ref +zbar_symbol_xml +zbar_version +zbar_video_create +zbar_video_destroy +zbar_video_enable +zbar_video_get_fd +zbar_video_get_format +zbar_video_get_height +zbar_video_get_width +zbar_video_init +zbar_video_next_image +zbar_video_open +zbar_video_request_interface +zbar_video_request_iomode +zbar_video_request_size +zbar_window_attach +zbar_window_create +zbar_window_destroy +zbar_window_draw +zbar_window_get_overlay +zbar_window_redraw +zbar_window_resize +zbar_window_set_overlay diff --git a/Software/External_Repositories/ZBar/lib/libzbar-0.lib b/Software/External_Repositories/ZBar/lib/libzbar-0.lib new file mode 100644 index 000000000..7b15f726d Binary files /dev/null and b/Software/External_Repositories/ZBar/lib/libzbar-0.lib differ diff --git a/Software/External_Repositories/ZBar/lib/libzbar.dll.a b/Software/External_Repositories/ZBar/lib/libzbar.dll.a new file mode 100644 index 000000000..a6d7f8acf Binary files /dev/null and b/Software/External_Repositories/ZBar/lib/libzbar.dll.a differ diff --git a/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk b/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk index cd539235a..09f542836 100644 Binary files a/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk and b/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk differ diff --git a/Software/Visual_Studio/Build/Shortcuts/Machine Studio.lnk b/Software/Visual_Studio/Build/Shortcuts/Machine Studio.lnk index 691007139..ff9db3779 100644 Binary files a/Software/Visual_Studio/Build/Shortcuts/Machine Studio.lnk and b/Software/Visual_Studio/Build/Shortcuts/Machine Studio.lnk differ diff --git a/Software/Visual_Studio/Build/Shortcuts/Proto Compiler GUI.lnk b/Software/Visual_Studio/Build/Shortcuts/Proto Compiler GUI.lnk index 8264c04b0..86ac8562d 100644 Binary files a/Software/Visual_Studio/Build/Shortcuts/Proto Compiler GUI.lnk and b/Software/Visual_Studio/Build/Shortcuts/Proto Compiler GUI.lnk differ diff --git a/Software/Visual_Studio/Build/Shortcuts/Stubs Execution GUI.lnk b/Software/Visual_Studio/Build/Shortcuts/Stubs Execution GUI.lnk index 00f4779a7..b0950b3d7 100644 Binary files a/Software/Visual_Studio/Build/Shortcuts/Stubs Execution GUI.lnk and b/Software/Visual_Studio/Build/Shortcuts/Stubs Execution GUI.lnk differ diff --git a/Software/Visual_Studio/Build/Shortcuts/Transport Router.lnk b/Software/Visual_Studio/Build/Shortcuts/Transport Router.lnk index c82e7cff2..5285bbf10 100644 Binary files a/Software/Visual_Studio/Build/Shortcuts/Transport Router.lnk and b/Software/Visual_Studio/Build/Shortcuts/Transport Router.lnk differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/CaptureConfig.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/CaptureConfig.cs index c1f5d4f48..9f54837cb 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/CaptureConfig.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/CaptureConfig.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using Tango.Core; using Tango.Core.Helpers; +using Tango.TCC.BL; namespace Tango.MachineStudio.ColorCapture.Models { @@ -67,6 +68,42 @@ namespace Tango.MachineStudio.ColorCapture.Models set { _sampleHeight = value; RaisePropertyChangedAuto(); } } + private bool _autoRelease; + public bool AutoRelease + { + get { return _autoRelease; } + set { _autoRelease = value; RaisePropertyChangedAuto(); } + } + + private double _similarityTolerance; + public double SimilarityTolerance + { + get { return _similarityTolerance; } + set { _similarityTolerance = value; RaisePropertyChangedAuto(); } + } + + private DeltaEComparisons _deltaEComparison; + public DeltaEComparisons DeltaEComparison + { + get { return _deltaEComparison; } + set { _deltaEComparison = value; RaisePropertyChangedAuto(); } + } + + private CardDetectionHistogramMethods _histogramComparison; + public CardDetectionHistogramMethods HistogramComparison + { + get { return _histogramComparison; } + set { _histogramComparison = value; RaisePropertyChangedAuto(); } + } + + private bool _enableDoubleChecking; + public bool EnableDoubleChecking + { + get { return _enableDoubleChecking; } + set { _enableDoubleChecking = value; RaisePropertyChangedAuto(); } + } + + public CaptureConfig() { Columns = 10; @@ -74,9 +111,13 @@ namespace Tango.MachineStudio.ColorCapture.Models TargetIndex = 89; SampleWidth = 300; SampleHeight = 330; + SimilarityTolerance = 50; SamplesFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "TCC Samples"); BenchmarksFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "TCC", "benchmarks_rgb_lab.csv"); TemplateFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "TCC", "template.bmp"); + DeltaEComparison = DeltaEComparisons.CieDe2000; + HistogramComparison = CardDetectionHistogramMethods.Chi_Square; + EnableDoubleChecking = true; } } } diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/DeltaEComparisons.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/DeltaEComparisons.cs new file mode 100644 index 000000000..8f3ab87c8 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Models/DeltaEComparisons.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.MachineStudio.ColorCapture.Models +{ + public enum DeltaEComparisons + { + Cie1976, + Cie94, + CieDe2000, + Cmc + } +} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj index 1679f2af9..c30520f13 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj @@ -104,6 +104,7 @@ + Code @@ -150,6 +151,10 @@ {f441feee-322a-4943-b566-110e12fd3b72} Tango.BL + + {6efd5895-177b-4bbb-af52-29f4d53b3fbd} + Tango.CircularGauge + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} Tango.Core diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs index 5b9d20bc4..21bb0baf0 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs @@ -37,6 +37,8 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels private BitmapSource _last_original_source; private DetectionOutput _last_detection_output; private byte[] templateBitmap; + private DeltaEComparisons _lastDeltaEComparison; + private IColorSpaceComparison _deltaEComparison; public IVideoCaptureProvider VideoProvider { get; set; } @@ -124,6 +126,20 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels set { _config = value; RaisePropertyChangedAuto(); } } + private bool _isPaused; + public bool IsPaused + { + get { return _isPaused; } + set { _isPaused = value; RaisePropertyChangedAuto(); } + } + + private double _similarity; + public double Similarity + { + get { return _similarity; } + set { _similarity = value; RaisePropertyChangedAuto(); } + } + public RelayCommand ImportBenchmarksCommand { get; set; } public RelayCommand ExportBenchmarksCommand { get; set; } @@ -233,6 +249,19 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels var rgb = new Lab(MeasureL, _measureA, _measureB).ToRgb(); Color refColor = Color.FromArgb(255, (byte)rgb.R, (byte)rgb.G, (byte)rgb.B); + CsvFile means_csv = new CsvFile(new CsvDestination(sample_folder + "\\means.csv")); + + foreach (var item in ColorDetector.EmptyColorMatrixFiducials(new DetectionInput() + { + Columns = Config.Columns, + Rows = Config.Rows, + }, _last_detection_output)) + { + means_csv.Append(item); + } + + means_csv.Dispose(); + var captureItem = new CaptureItem() { Image = rectified_file, @@ -250,6 +279,8 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels File.WriteAllText(capture_item_file, captureItem.ToJsonString()); CaptureItems.Insert(0, captureItem); + + Clear(); } catch (Exception ex) { @@ -313,13 +344,17 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels { if (SelectedVideoDevice.IsStarted) { - _abort = true; - SelectedVideoDevice.Stop(); - ProcessedColor = System.Windows.Media.Colors.Transparent; - CapturedColor = System.Windows.Media.Colors.Transparent; - Colors = null; - DetectedSource = null; - CaptureDeltaEController.Clear(); + if (!IsPaused) + { + _abort = true; + SelectedVideoDevice.Stop(); + Clear(); + } + else + { + IsPaused = false; + Clear(); + } } else { @@ -330,6 +365,16 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels } } + private void Clear() + { + ProcessedColor = System.Windows.Media.Colors.Transparent; + CapturedColor = System.Windows.Media.Colors.Transparent; + Colors = null; + DetectedSource = null; + CaptureDeltaEController.Clear(); + Similarity = 0; + } + private void OnSelectedVideoDeviceChanged(CaptureDevice previousDevice, CaptureDevice newDevice) { if (previousDevice != null) @@ -346,7 +391,7 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels private async void OnVideoFrameReceived(object sender, Video.DirectShow.EventArguments.FrameReceivedEventArgs args) { - if (_abort) return; + if (_abort || IsPaused) return; if (_cardDetector.CanDetect) { @@ -359,10 +404,23 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels DesiredBitmapHeight = Config.SampleHeight, TargetIndex = Config.TargetIndex, TemplateBitmapBytes = templateBitmap, + SimilarityTolerance = Config.SimilarityTolerance, + HistogramMethod = Config.HistogramComparison, + EnableDoubleChecking = Config.EnableDoubleChecking, }); + if (result.Similarity > 0) + { + Similarity = result.Similarity; + } + if (result.IsDetected) { + if (Config.AutoRelease) + { + IsPaused = true; + } + _last_original_source = result.Source; _last_detection_output = result.ColorDetectionOutput; @@ -386,7 +444,7 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels //calculate delta E. Lab measureLab = new Lab(MeasureL, MeasureA, MeasureB); - DeltaE = measureLab.Compare(new Rgb(ProcessedColor.R, ProcessedColor.G, ProcessedColor.B), new CieDe2000Comparison()); + DeltaE = measureLab.Compare(new Rgb(ProcessedColor.R, ProcessedColor.G, ProcessedColor.B), GetDeltaEComparison()); }); } } @@ -432,5 +490,32 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels SettingsManager.Default.GetOrCreate().Config = Config; SettingsManager.Default.Save(); } + + private IColorSpaceComparison GetDeltaEComparison() + { + if (_lastDeltaEComparison != Config.DeltaEComparison || _deltaEComparison == null) + { + _lastDeltaEComparison = Config.DeltaEComparison; + + if (Config.DeltaEComparison == DeltaEComparisons.Cie1976) + { + _deltaEComparison = new Cie1976Comparison(); + } + else if (Config.DeltaEComparison == DeltaEComparisons.Cie94) + { + _deltaEComparison = new Cie94Comparison(); + } + else if (Config.DeltaEComparison == DeltaEComparisons.CieDe2000) + { + _deltaEComparison = new CieDe2000Comparison(); + } + else if (Config.DeltaEComparison == DeltaEComparisons.Cmc) + { + _deltaEComparison = new CmcComparison(); + } + } + + return _deltaEComparison; + } } } diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml index c1555fbcd..d6f4890d7 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml @@ -1,9 +1,11 @@  - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -62,6 +77,7 @@ + @@ -82,6 +98,22 @@ + + + + + + + + + + + + + + + + @@ -91,27 +123,94 @@ - - - - - + + + + + + + + + + + + + + + + + + + - + + + Similarity + + + + + + + + + @@ -206,6 +305,7 @@ + @@ -265,7 +365,7 @@ - + @@ -312,10 +412,17 @@ - - Delta E Distance + + + Delta E Distance + + ( + + ) + + - + @@ -358,7 +465,7 @@ - + @@ -454,23 +561,27 @@ - + - - - + + + - - - - - - - + + + + + + + - + + + + + Columns @@ -490,19 +601,42 @@ Samples Folder - + Benchmarks - + Template - + + + x + + + + + - + + + Similarity Tolerance + + + + Auto Release + + + Delta E Standard + + + Histogram Similarity + + + Double Checking + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Video/DefaultVideoCaptureProvider.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Video/DefaultVideoCaptureProvider.cs index 3aa9d4c88..f710cf082 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Video/DefaultVideoCaptureProvider.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Video/DefaultVideoCaptureProvider.cs @@ -28,16 +28,17 @@ namespace Tango.MachineStudio.Common.Video var availableDevices = CaptureDevice.GetAvailableCaptureDevices(); - for (int i = 0; i < 3; i++) + for (int i = 0; i < availableDevices.Count; i++) { - if (i > availableDevices.Count - 1) - { - AvailableCaptureDevices.Add(new CaptureDevice() { Device = null }); - } - else + AvailableCaptureDevices.Add(new CaptureDevice() { Device = availableDevices[i] }); + } + + while (AvailableCaptureDevices.Count < 3) + { + AvailableCaptureDevices.Add(new CaptureDevice() { - AvailableCaptureDevices.Add(new CaptureDevice() { Device = availableDevices[i] }); - } + Device = null, + }); } } } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MachineConnectionView.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MachineConnectionView.xaml index 9794404b9..7c4e960ec 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MachineConnectionView.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MachineConnectionView.xaml @@ -124,7 +124,7 @@ - + Keep Alive diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/App.config b/Software/Visual_Studio/TCC/Tango.TCC.BL/App.config new file mode 100644 index 000000000..8f1b939fd --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/App.config @@ -0,0 +1,45 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs index bd8b394ad..e63e0bdd1 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs @@ -16,6 +16,9 @@ namespace Tango.TCC.BL public int Rows { get; set; } public int TargetIndex { get; set; } public byte[] TemplateBitmapBytes { get; set; } + public double SimilarityTolerance { get; set; } + public CardDetectionHistogramMethods HistogramMethod { get; set; } + public bool EnableDoubleChecking { get; set; } public CardDetectionConfig() { @@ -24,7 +27,10 @@ namespace Tango.TCC.BL Columns = 10; Rows = 11; TargetIndex = 89; + SimilarityTolerance = 20; Benchmarks = new List(); + HistogramMethod = CardDetectionHistogramMethods.Intersection; + EnableDoubleChecking = true; } } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionHistogramMethods.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionHistogramMethods.cs new file mode 100644 index 000000000..26125ea3f --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionHistogramMethods.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.TCC.BL +{ + public enum CardDetectionHistogramMethods + { + Correlation = 0, + Chi_Square = 1, + Intersection = 2, + BhattacharyyaDistance = 3, + None = 4, + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs index 0cf7f3d67..117317dcd 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs @@ -14,5 +14,7 @@ namespace Tango.TCC.BL public BitmapSource Source { get; set; } public BitmapSource DetectedBitmap { get; set; } public DetectionOutput ColorDetectionOutput { get; set; } + public double Similarity { get; set; } + public String Barcode { get; set; } } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs index c0f827e1b..50bcceeba 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs @@ -17,6 +17,7 @@ namespace Tango.TCC.BL private ColorDetector _colorDetector; private static byte[] _defaultTemplate; private static List _defaultBenchmarks; + private byte[] _sourceBitmapBytes; private bool _canDetect; public bool CanDetect @@ -54,14 +55,22 @@ namespace Tango.TCC.BL CardDetectionResult detectionResult = new CardDetectionResult(); detectionResult.Source = cloned; + _sourceBitmapBytes = cloned.ToBytes(PixelFormats.Rgb24); + Tango.TCC.CardDetector.CardDetection detector = new TCC.CardDetector.CardDetection(); - var result = detector.Detect(cloned.ToBytes(PixelFormats.Rgb24), new TCC.CardDetector.CardDetectionConfig() + var result = detector.Detect(_sourceBitmapBytes, new TCC.CardDetector.CardDetectionConfig() { DesiredBitmapWidth = config.DesiredBitmapWidth, DesiredBitmapHeight = config.DesiredBitmapHeight, - TemplateBitmap = config.TemplateBitmapBytes != null ? config.TemplateBitmapBytes : _defaultTemplate.ToArray(), + TemplateBitmap = config.TemplateBitmapBytes != null ? config.TemplateBitmapBytes : _defaultTemplate, + SimilarityTolerance = config.SimilarityTolerance, + HistogramMethod = (int)config.HistogramMethod, + EnableDoubleChecking = config.EnableDoubleChecking, }); + detectionResult.Similarity = result.Similarity; + detectionResult.Barcode = result.Barcode; + if (result.IsDetected) { detectionResult.IsDetected = true; diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/ColorDetector.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/ColorDetector.cs index a41124c42..d8a7f9dc7 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/ColorDetector.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/ColorDetector.cs @@ -148,6 +148,25 @@ namespace Tango.TCC.BL return bmp; } + public static IEnumerable EmptyColorMatrixFiducials(DetectionInput detectionInput, DetectionOutput detectionOutput) + { + List colors = new List(); + + for (int i = 0; i < detectionOutput.ColorMatrix.Count; i++) + { + if (!IsDetectedColorFiducial(i + 1, detectionInput.Columns, detectionInput.Rows)) + { + colors.Add(detectionOutput.ColorMatrix[i]); + } + else + { + colors.Add(new DetectionColor()); + } + } + + return colors; + } + public static bool IsDetectedColorFiducial(int index, int columns, int rows) { return index == 1 || index == columns || index == columns * rows || index == (columns * rows) - columns + 1; @@ -159,7 +178,7 @@ namespace Tango.TCC.BL return benchmarks; } - public static void SaveBenchmarks(String file,IEnumerable benchmarks) + public static void SaveBenchmarks(String file, IEnumerable benchmarks) { CsvFile csvFile = new CsvFile(new CsvDestination(file)); diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/Device.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/Device.cs new file mode 100644 index 000000000..8283ef332 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/Device.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.TCC.BL.Entities +{ + [Table("DEVICES")] + public class Device : TCCEntityBase + { + [Column("DEVICE_ID")] + public String DeviceID { get; set; } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCContext.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCContext.cs new file mode 100644 index 000000000..5770bf1d0 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCContext.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.TCC.BL.Entities +{ + public class TCCContext : DbContext + { + private DataSource _dataSource; + + /// + /// Initializes a new instance of the class. + /// + public TCCContext() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The data source. + public TCCContext(DataSource dataSource) : base(dataSource.ToConnection(), true) + { + _dataSource = dataSource; + Database.SetInitializer(null); + Configuration.LazyLoadingEnabled = false; + } + + /// + /// Gets or sets the devices. + /// + public DbSet Devices + { + get; set; + } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCEntityBase.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCEntityBase.cs new file mode 100644 index 000000000..9e3778a3c --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Entities/TCCEntityBase.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.TCC.BL.Entities +{ + public class TCCEntityBase + { + [Column("ID")] + public int ID { get; set; } + + [Column("GUID")] + public String Guid { get; set; } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj b/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj index bb12c02cc..7d677edc3 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj @@ -31,12 +31,19 @@ 4 + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll + @@ -55,15 +62,24 @@ + + + + + + + + TCC\benchmarks_rgb_lab.csv PreserveNewest + @@ -110,6 +126,7 @@ PreserveNewest + diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionRequest.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionRequest.cs index 2ee202acc..ba411dda6 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionRequest.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionRequest.cs @@ -9,7 +9,7 @@ namespace Tango.TCC.BL.Web { public class ColorDetectionRequest : WebRequestMessage { - public int Number { get; set; } public String BitmapString { get; set; } + public String Barcode { get; set; } } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionResponse.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionResponse.cs index a58d1a414..afc53b26c 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionResponse.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/ColorDetectionResponse.cs @@ -10,8 +10,6 @@ namespace Tango.TCC.BL.Web { public class ColorDetectionResponse : WebResponseMessage { - public int Number { get; set; } - public DetectionColor RawColor { get; set; } public DetectionColor ProcessedColor { get; set; } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionRequest.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionRequest.cs new file mode 100644 index 000000000..6082fedaf --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Transport.Web; + +namespace Tango.TCC.BL.Web +{ + public class DefinitionRequest : WebRequestMessage + { + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs new file mode 100644 index 000000000..d6161e738 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Transport.Web; + +namespace Tango.TCC.BL.Web +{ + public class DefinitionResponse : WebResponseMessage + { + public String TemplateString { get; set; } + public int SampleWidth { get; set; } + public int SampleHeight { get; set; } + public int CameraWidth { get; set; } + public int CameraHeight { get; set; } + public double SimilarityTolerance { get; set; } + public CardDetectionHistogramMethods HistogramMethod { get; set; } + public bool EnableDoubleChecking { get; set; } + + public DefinitionResponse() + { + SampleWidth = 300; + SampleHeight = 330; + CameraWidth = 1280; + CameraHeight = 720; + SimilarityTolerance = 50; + HistogramMethod = CardDetectionHistogramMethods.Chi_Square; + EnableDoubleChecking = true; + } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginRequest.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginRequest.cs new file mode 100644 index 000000000..5f3a66daa --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginRequest.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Transport.Web; + +namespace Tango.TCC.BL.Web +{ + public class LoginRequest : WebRequestMessage + { + public String Email { get; set; } + public String Password { get; set; } + public String AppID { get; set; } + + public String Device { get; set; } + public String DeviceID { get; set; } + public String OSVersion { get; set; } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginResponse.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginResponse.cs new file mode 100644 index 000000000..054e18ce1 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/LoginResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Transport.Web; +using Tango.Web.Security; + +namespace Tango.TCC.BL.Web +{ + public class LoginResponse : WebTokenResponse + { + + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/packages.config b/Software/Visual_Studio/TCC/Tango.TCC.BL/packages.config index fa3c0d58d..08b9dd27c 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/packages.config +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp index 52721368a..91b38c55c 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp @@ -24,6 +24,7 @@ vector ArucoUtils::getArcusVertices(Mat image) aruco::DetectorParameters* params = new aruco::DetectorParameters(); params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; params->perspectiveRemovePixelPerCell = 50; + params->minDistanceToBorder = 0; aruco::detectMarkers(image, dictionary, corners, ids, &(*params)); diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp index 3f3415e48..174d2d549 100644 Binary files a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h index 05b137a7b..0f1a87d45 100644 Binary files a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h index a6e104b53..86d44ca34 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h @@ -14,6 +14,9 @@ namespace Tango property double DesiredBitmapWidth; property double DesiredBitmapHeight; property cli::array^ TemplateBitmap; + property double SimilarityTolerance; + property int HistogramMethod; + property bool EnableDoubleChecking; }; } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h index 68507e1f4..ad15b6ae4 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h @@ -14,6 +14,8 @@ namespace Tango CardDetectionResult(); property bool IsDetected; property cli::array^ DetectedBitmap; + property double Similarity; + property System::String^ Barcode; }; } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj index 2ee6e7a2d..11af99029 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj @@ -94,11 +94,11 @@ Level3 Disabled WIN32;_DEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\Tango.TCC.OpenCV;$(SolutionDir)..\External_Repositories\OpenCV;$(SolutionDir)..\External_Repositories\OpenCV\opencv_contrib-master\modules\aruco\include;%(AdditionalIncludeDirectories) + $(ProjectDir)..\Tango.TCC.OpenCV;$(SolutionDir)..\External_Repositories\OpenCV;$(SolutionDir)..\External_Repositories\OpenCV\opencv_contrib-master\modules\aruco\include;$(SolutionDir)..\External_Repositories\ZBar\include;%(AdditionalIncludeDirectories) - opencv_calib3d330d.lib;opencv_core330d.lib;opencv_features2d330d.lib;opencv_flann330d.lib;opencv_highgui330d.lib;opencv_imgproc330d.lib;opencv_imgcodecs330d.lib;opencv_ml330d.lib;opencv_objdetect330d.lib;opencv_photo330d.lib;opencv_stitching330d.lib;opencv_superres330d.lib;opencv_video330d.lib;opencv_videostab330d.lib;opencv_videoio330d.lib;opencv_aruco330d.lib;%(AdditionalDependencies) - $(SolutionDir)..\External_Repositories\OpenCV\bin;%(AdditionalLibraryDirectories) + opencv_calib3d330d.lib;opencv_core330d.lib;opencv_features2d330d.lib;opencv_flann330d.lib;opencv_highgui330d.lib;opencv_imgproc330d.lib;opencv_imgcodecs330d.lib;opencv_ml330d.lib;opencv_objdetect330d.lib;opencv_photo330d.lib;opencv_stitching330d.lib;opencv_superres330d.lib;opencv_video330d.lib;opencv_videostab330d.lib;opencv_videoio330d.lib;opencv_aruco330d.lib;libzbar-0.lib;%(AdditionalDependencies) + $(SolutionDir)..\External_Repositories\OpenCV\bin;$(SolutionDir)..\External_Repositories\ZBar\lib;%(AdditionalLibraryDirectories) diff --git a/Software/Visual_Studio/TCC/Tango.TCC.OpenCV.DLL/Tango.TCC.OpenCV.DLL.csproj b/Software/Visual_Studio/TCC/Tango.TCC.OpenCV.DLL/Tango.TCC.OpenCV.DLL.csproj index 1356ad9b7..415d51a6e 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.OpenCV.DLL/Tango.TCC.OpenCV.DLL.csproj +++ b/Software/Visual_Studio/TCC/Tango.TCC.OpenCV.DLL/Tango.TCC.OpenCV.DLL.csproj @@ -161,6 +161,42 @@ ZedGraph.resources.dll Always + + libiconv-2.dll + Always + + + libjpeg-7.dll + Always + + + libMagickCore-2.dll + Always + + + libMagickWand-2.dll + Always + + + libpng12-0.dll + Always + + + libtiff-3.dll + Always + + + libxml2-2.dll + Always + + + libzbar-0.dll + Always + + + zlib1.dll + Always + diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs b/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs index ade4f72f3..b3a4d4808 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs @@ -14,26 +14,45 @@ namespace Tango.TCC.Service.Controllers { public class ColorDetectionController : TangoController { + [HttpPost] + public LoginResponse Login(LoginRequest request) + { + return new LoginResponse(); + } + + [HttpPost] + public DefinitionResponse GetDefinition(DefinitionRequest request) + { + return new DefinitionResponse() + { + TemplateString = TCCServiceConfig.TEMPLATE_STRING, + SampleWidth = TCCServiceConfig.SAMPLE_WIDTH, + SampleHeight = TCCServiceConfig.SAMPLE_HEIGHT, + CameraWidth = TCCServiceConfig.CAMERA_WIDTH, + CameraHeight = TCCServiceConfig.CAMERA_HEIGHT, + HistogramMethod = TCCServiceConfig.HISTOGRAM_METHOD, + SimilarityTolerance = TCCServiceConfig.SIMILARITY_TOLERANCE, + EnableDoubleChecking = TCCServiceConfig.ENABLE_DOUBLE_CHECKING, + }; + } + [HttpPost] public ColorDetectionResponse Detect(ColorDetectionRequest request) { - byte[] bitmapBytes = System.Convert.FromBase64String(request.BitmapString); + byte[] bitmapBytes = Convert.FromBase64String(request.BitmapString); using (ColorDetector detector = new ColorDetector()) { var output = detector.Detect(new DetectionInput() { - Number = request.Number, Bitmap = ByteString.CopyFrom(bitmapBytes), Columns = TCCServiceConfig.CARD_COLUMNS, Rows = TCCServiceConfig.CARD_ROWS, TargetIndex = TCCServiceConfig.CARD_TARGET_INDEX, - DebugMode = true, }); return new ColorDetectionResponse() { - Number = (int)output.Number, ProcessedColor = output.ProcessedColor, RawColor = output.RawColor, }; diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs b/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs index 8cacbe636..8bedaefe9 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs @@ -3,12 +3,15 @@ using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Web; +using Tango.TCC.BL; using Tango.Web; namespace Tango.TCC.Service { public class TCCServiceConfig : WebConfig { + public static String JWT_TOKEN_SECRET => ConfigurationManager.AppSettings[nameof(JWT_TOKEN_SECRET)].ToString(); + /// /// Gets the number of card columns. /// @@ -23,5 +26,50 @@ namespace Tango.TCC.Service /// Gets the card target index (hole). /// public static int CARD_TARGET_INDEX => int.Parse(ConfigurationManager.AppSettings[nameof(CARD_TARGET_INDEX)].ToString()); + + /// + /// Gets the template image base64 string. + /// + public static String TEMPLATE_STRING => ConfigurationManager.AppSettings[nameof(TEMPLATE_STRING)].ToString(); + + /// + /// Gets the desired width of the sample image. + /// + public static int SAMPLE_WIDTH => int.Parse(ConfigurationManager.AppSettings[nameof(SAMPLE_WIDTH)].ToString()); + + /// + /// Gets the desired height of the sample image. + /// + public static int SAMPLE_HEIGHT => int.Parse(ConfigurationManager.AppSettings[nameof(SAMPLE_HEIGHT)].ToString()); + + /// + /// Gets the recommended camera resolution width. + /// + public static int CAMERA_WIDTH => int.Parse(ConfigurationManager.AppSettings[nameof(CAMERA_WIDTH)].ToString()); + + /// + /// Gets the recommended camera resolution height. + /// + public static int CAMERA_HEIGHT => int.Parse(ConfigurationManager.AppSettings[nameof(CAMERA_HEIGHT)].ToString()); + + /// + /// Gets the histogram similarity tolerance. + /// + public static double SIMILARITY_TOLERANCE => int.Parse(ConfigurationManager.AppSettings[nameof(SIMILARITY_TOLERANCE)].ToString()); + + /// + /// Gets the histogram comparison method. + /// + public static CardDetectionHistogramMethods HISTOGRAM_METHOD => (CardDetectionHistogramMethods)Enum.Parse(typeof(CardDetectionHistogramMethods), ConfigurationManager.AppSettings[nameof(HISTOGRAM_METHOD)].ToString()); + + /// + /// Gets a value indicating whether to perform a double check of the card arucos. + /// + public static bool ENABLE_DOUBLE_CHECKING => bool.Parse(ConfigurationManager.AppSettings[nameof(ENABLE_DOUBLE_CHECKING)].ToString()); + + /// + /// Gets the mobile application ID. + /// + public static String APP_ID => ConfigurationManager.AppSettings[nameof(APP_ID)].ToString(); } } \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/Tango.TCC.Service.csproj b/Software/Visual_Studio/TCC/Tango.TCC.Service/Tango.TCC.Service.csproj index 43a493f8e..78e13e6d4 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/Tango.TCC.Service.csproj +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/Tango.TCC.Service.csproj @@ -46,6 +46,12 @@ 4 + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config b/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config index 754e88280..d2433dc9b 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config @@ -4,15 +4,43 @@ https://go.microsoft.com/fwlink/?LinkId=301879 --> + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + @@ -34,12 +62,13 @@ - + - + + @@ -49,7 +78,7 @@ - + @@ -102,4 +131,10 @@ - + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/packages.config b/Software/Visual_Studio/TCC/Tango.TCC.Service/packages.config index 34d59e98c..115c40d26 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/packages.config +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/packages.config @@ -2,6 +2,7 @@ + diff --git a/Software/Visual_Studio/Tango.SharedUI/Controls/DoubleClickDataGrid.cs b/Software/Visual_Studio/Tango.SharedUI/Controls/DoubleClickDataGrid.cs index 85b104aa8..f12a6c322 100644 --- a/Software/Visual_Studio/Tango.SharedUI/Controls/DoubleClickDataGrid.cs +++ b/Software/Visual_Studio/Tango.SharedUI/Controls/DoubleClickDataGrid.cs @@ -28,7 +28,7 @@ namespace Tango.SharedUI.Controls private void DoubleClickDataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) { - if (SelectedItem != null) + if (SelectedItem != null && (e.OriginalSource as UIElement != null) && (e.OriginalSource as FrameworkElement).DataContext == SelectedItem) { DoubleClickCommand?.Execute(SelectedItem); } diff --git a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs index ae5c66f6e..59237b4a0 100644 --- a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs +++ b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs @@ -10,10 +10,12 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Tango.Core; using Tango.Core.IO; using Tango.CSV; using Tango.PMR.TCC; using Tango.TCC.BL; +using Tango.TCC.BL.Entities; namespace Tango.UnitTesting.TCC { @@ -82,6 +84,23 @@ namespace Tango.UnitTesting.TCC } } + [TestMethod] + public void Test_Database() + { + using (TCCContext db = new TCCContext(new DataSource() + { + Address = "localhost\\SQLEXPRESS", + Catalog = "TCC", + IntegratedSecurity = true, + Type = DataSourceType.SQLServer, + })) + { + var device = db.Devices.FirstOrDefault(); + Assert.IsNotNull(device); + db.SaveChanges(); + } + } + private byte[] GetBitmapData(byte[] bitmap) { using (MemoryStream ms = new MemoryStream(bitmap)) -- cgit v1.3.1 From 080f1697e97e13461ec6df4d31c8924d01257a1b Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Tue, 9 Apr 2019 01:47:48 +0300 Subject: MERGE --- .gitignore | 3 + .../colorcapture/controls/ToggleImageButton.java | 122 + .../twine/colorcapture/mvvm/BindingAdapters.java | 9 + .../twine/colorcapture/mvvm/BindingConverters.java | 17 + .../colorcapture/views/home/HomeFragment.java | 39 +- .../views/loading/LoadingFragmentVM.java | 4 +- .../colorcapture/views/main/MainActivity.java | 11 +- .../colorcapture/views/main/MainActivityVM.java | 25 + .../app/src/main/res/drawable-hdpi/logo.png | Bin 0 -> 14712 bytes .../app/src/main/res/drawable-hdpi/more_normal.png | Bin 0 -> 2778 bytes .../src/main/res/drawable-hdpi/more_selected.png | Bin 0 -> 6366 bytes .../app/src/main/res/drawable-hdpi/my_colors.png | Bin 0 -> 3888 bytes .../app/src/main/res/drawable-hdpi/selected.png | Bin 0 -> 8549 bytes .../app/src/main/res/drawable-hdpi/twine_logo.png | Bin 0 -> 11261 bytes .../main/res/drawable-hdpi/twinesnap_normal.png | Bin 0 -> 3585 bytes .../main/res/drawable-hdpi/twinesnap_selected.png | Bin 0 -> 7914 bytes .../app/src/main/res/drawable-mdpi/logo.png | Bin 0 -> 9261 bytes .../app/src/main/res/drawable-mdpi/more_normal.png | Bin 0 -> 1658 bytes .../src/main/res/drawable-mdpi/more_selected.png | Bin 0 -> 3457 bytes .../app/src/main/res/drawable-mdpi/my_colors.png | Bin 0 -> 2340 bytes .../app/src/main/res/drawable-mdpi/selected.png | Bin 0 -> 4651 bytes .../app/src/main/res/drawable-mdpi/twine_logo.png | Bin 0 -> 7143 bytes .../main/res/drawable-mdpi/twinesnap_normal.png | Bin 0 -> 2208 bytes .../main/res/drawable-mdpi/twinesnap_selected.png | Bin 0 -> 4338 bytes .../app/src/main/res/drawable-xhdpi/logo.png | Bin 0 -> 21247 bytes .../src/main/res/drawable-xhdpi/more_normal.png | Bin 0 -> 3755 bytes .../src/main/res/drawable-xhdpi/more_selected.png | Bin 0 -> 9912 bytes .../app/src/main/res/drawable-xhdpi/my_colors.png | Bin 0 -> 5574 bytes .../app/src/main/res/drawable-xhdpi/selected.png | Bin 0 -> 12524 bytes .../app/src/main/res/drawable-xhdpi/twine_logo.png | Bin 0 -> 16023 bytes .../main/res/drawable-xhdpi/twinesnap_normal.png | Bin 0 -> 4973 bytes .../main/res/drawable-xhdpi/twinesnap_selected.png | Bin 0 -> 12333 bytes .../app/src/main/res/drawable-xxhdpi/logo.png | Bin 0 -> 35163 bytes .../src/main/res/drawable-xxhdpi/more_normal.png | Bin 0 -> 6055 bytes .../src/main/res/drawable-xxhdpi/more_selected.png | Bin 0 -> 19207 bytes .../app/src/main/res/drawable-xxhdpi/my_colors.png | Bin 0 -> 8326 bytes .../app/src/main/res/drawable-xxhdpi/selected.png | Bin 0 -> 23601 bytes .../src/main/res/drawable-xxhdpi/twine_logo.png | Bin 0 -> 26389 bytes .../main/res/drawable-xxhdpi/twinesnap_normal.png | Bin 0 -> 7911 bytes .../res/drawable-xxhdpi/twinesnap_selected.png | Bin 0 -> 23276 bytes .../app/src/main/res/drawable-xxxhdpi/logo.png | Bin 0 -> 51829 bytes .../src/main/res/drawable-xxxhdpi/more_normal.png | Bin 0 -> 8485 bytes .../main/res/drawable-xxxhdpi/more_selected.png | Bin 0 -> 31105 bytes .../src/main/res/drawable-xxxhdpi/my_colors.png | Bin 0 -> 11825 bytes .../app/src/main/res/drawable-xxxhdpi/selected.png | Bin 0 -> 37818 bytes .../src/main/res/drawable-xxxhdpi/twine_logo.png | Bin 0 -> 38186 bytes .../main/res/drawable-xxxhdpi/twinesnap_normal.png | Bin 0 -> 10943 bytes .../res/drawable-xxxhdpi/twinesnap_selected.png | Bin 0 -> 37678 bytes .../src/main/res/drawable/background_gradient.xml | 8 + .../app/src/main/res/drawable/border.xml | 8 + .../app/src/main/res/drawable/border_shadow.xml | 19 + .../app/src/main/res/layout/activity_main.xml | 40 +- .../app/src/main/res/layout/fragment_capture.xml | 26 +- .../app/src/main/res/layout/fragment_loading.xml | 50 +- .../app/src/main/res/values/colors.xml | 2 + .../app/src/main/res/values/strings.xml | 2 + .../app/src/main/res/values/styles.xml | 6 + Software/Android_Studio/settings.jar | Bin 0 -> 5837 bytes Software/DB/Tango.mdf | Bin 75497472 -> 75497472 bytes Software/DB/Tango_log.ldf | Bin 22675456 -> 22675456 bytes .../Embedded_SW/Embedded/Common/SW_Info/SW_Info.c | 2 +- .../Embedded/Modules/AlarmHandling/AlarmHandling.c | 2 +- .../Embedded/Modules/Control/MillisecTask.c | 47 +- .../Embedded/Modules/Control/MillisecTask.h | 2 + .../Embedded/Modules/Diagnostics/Diagnostics.c | 22 +- .../Embedded/Modules/Heaters/Heaters_print.c | 166 +- .../StateMachines/Initialization/PowerIdle.c | 22 +- .../OpenCV/bin/opencv_aruco330d.exp | Bin 0 -> 166117 bytes .../OpenCV/bin/opencv_aruco330d.lib | Bin 0 -> 279190 bytes .../OpenCV/bin/opencv_calib3d330d.exp | Bin 0 -> 185986 bytes .../OpenCV/bin/opencv_calib3d330d.lib | Bin 0 -> 314642 bytes .../OpenCV/bin/opencv_core330d.exp | Bin 0 -> 452112 bytes .../OpenCV/bin/opencv_core330d.lib | Bin 0 -> 752684 bytes .../OpenCV/bin/opencv_dnn330d.exp | Bin 0 -> 240989 bytes .../OpenCV/bin/opencv_dnn330d.lib | Bin 0 -> 401550 bytes .../OpenCV/bin/opencv_features2d330d.exp | Bin 0 -> 199127 bytes .../OpenCV/bin/opencv_features2d330d.lib | Bin 0 -> 338980 bytes .../OpenCV/bin/opencv_flann330d.exp | Bin 0 -> 116331 bytes .../OpenCV/bin/opencv_flann330d.lib | Bin 0 -> 197374 bytes .../OpenCV/bin/opencv_highgui330d.exp | Bin 0 -> 132361 bytes .../OpenCV/bin/opencv_highgui330d.lib | Bin 0 -> 225822 bytes .../OpenCV/bin/opencv_imgcodecs330d.exp | Bin 0 -> 113245 bytes .../OpenCV/bin/opencv_imgcodecs330d.lib | Bin 0 -> 194388 bytes .../OpenCV/bin/opencv_imgproc330d.exp | Bin 0 -> 200907 bytes .../OpenCV/bin/opencv_imgproc330d.lib | Bin 0 -> 342470 bytes .../OpenCV/bin/opencv_ml330d.exp | Bin 0 -> 141459 bytes .../OpenCV/bin/opencv_ml330d.lib | Bin 0 -> 237154 bytes .../OpenCV/bin/opencv_objdetect330d.exp | Bin 0 -> 142897 bytes .../OpenCV/bin/opencv_objdetect330d.lib | Bin 0 -> 244502 bytes .../OpenCV/bin/opencv_photo330d.exp | Bin 0 -> 161079 bytes .../OpenCV/bin/opencv_photo330d.lib | Bin 0 -> 272214 bytes .../OpenCV/bin/opencv_shape330d.exp | Bin 0 -> 135389 bytes .../OpenCV/bin/opencv_shape330d.lib | Bin 0 -> 228274 bytes .../OpenCV/bin/opencv_stitching330d.exp | Bin 0 -> 435599 bytes .../OpenCV/bin/opencv_stitching330d.lib | Bin 0 -> 734718 bytes .../OpenCV/bin/opencv_superres330d.exp | Bin 0 -> 164965 bytes .../OpenCV/bin/opencv_superres330d.lib | Bin 0 -> 280620 bytes .../OpenCV/bin/opencv_ts330d.lib | Bin 0 -> 15619832 bytes .../OpenCV/bin/opencv_video330d.exp | Bin 0 -> 132523 bytes .../OpenCV/bin/opencv_video330d.lib | Bin 0 -> 224086 bytes .../OpenCV/bin/opencv_videoio330d.exp | Bin 0 -> 119387 bytes .../OpenCV/bin/opencv_videoio330d.lib | Bin 0 -> 203624 bytes .../OpenCV/bin/opencv_videostab330d.exp | Bin 0 -> 313665 bytes .../OpenCV/bin/opencv_videostab330d.lib | Bin 0 -> 529376 bytes .../Graphics/Mobile/zeplin/drawable-hdpi/logo.png | Bin 0 -> 14712 bytes .../Mobile/zeplin/drawable-hdpi/more_normal.png | Bin 0 -> 2778 bytes .../Mobile/zeplin/drawable-hdpi/more_selected.png | Bin 0 -> 6366 bytes .../Mobile/zeplin/drawable-hdpi/my_colors.png | Bin 0 -> 3888 bytes .../Mobile/zeplin/drawable-hdpi/selected.png | Bin 0 -> 8549 bytes .../zeplin/drawable-hdpi/twinesnap_normal.png | Bin 0 -> 3585 bytes .../zeplin/drawable-hdpi/twinesnap_selected.png | Bin 0 -> 7914 bytes .../Graphics/Mobile/zeplin/drawable-mdpi/logo.png | Bin 0 -> 9261 bytes .../Mobile/zeplin/drawable-mdpi/more_normal.png | Bin 0 -> 1658 bytes .../Mobile/zeplin/drawable-mdpi/more_selected.png | Bin 0 -> 3457 bytes .../Mobile/zeplin/drawable-mdpi/my_colors.png | Bin 0 -> 2340 bytes .../Mobile/zeplin/drawable-mdpi/selected.png | Bin 0 -> 4651 bytes .../zeplin/drawable-mdpi/twinesnap_normal.png | Bin 0 -> 2208 bytes .../zeplin/drawable-mdpi/twinesnap_selected.png | Bin 0 -> 4338 bytes .../Graphics/Mobile/zeplin/drawable-xhdpi/logo.png | Bin 0 -> 21247 bytes .../Mobile/zeplin/drawable-xhdpi/more_normal.png | Bin 0 -> 3755 bytes .../Mobile/zeplin/drawable-xhdpi/more_selected.png | Bin 0 -> 9912 bytes .../Mobile/zeplin/drawable-xhdpi/my_colors.png | Bin 0 -> 5574 bytes .../Mobile/zeplin/drawable-xhdpi/selected.png | Bin 0 -> 12524 bytes .../zeplin/drawable-xhdpi/twinesnap_normal.png | Bin 0 -> 4973 bytes .../zeplin/drawable-xhdpi/twinesnap_selected.png | Bin 0 -> 12333 bytes .../Mobile/zeplin/drawable-xxhdpi/logo.png | Bin 0 -> 35163 bytes .../Mobile/zeplin/drawable-xxhdpi/more_normal.png | Bin 0 -> 6055 bytes .../zeplin/drawable-xxhdpi/more_selected.png | Bin 0 -> 19207 bytes .../Mobile/zeplin/drawable-xxhdpi/my_colors.png | Bin 0 -> 8326 bytes .../Mobile/zeplin/drawable-xxhdpi/selected.png | Bin 0 -> 23601 bytes .../zeplin/drawable-xxhdpi/twinesnap_normal.png | Bin 0 -> 7911 bytes .../zeplin/drawable-xxhdpi/twinesnap_selected.png | Bin 0 -> 23276 bytes .../Mobile/zeplin/drawable-xxxhdpi/logo.png | Bin 0 -> 51829 bytes .../Mobile/zeplin/drawable-xxxhdpi/more_normal.png | Bin 0 -> 8485 bytes .../zeplin/drawable-xxxhdpi/more_selected.png | Bin 0 -> 31105 bytes .../Mobile/zeplin/drawable-xxxhdpi/my_colors.png | Bin 0 -> 11825 bytes .../Mobile/zeplin/drawable-xxxhdpi/selected.png | Bin 0 -> 37818 bytes .../zeplin/drawable-xxxhdpi/twinesnap_normal.png | Bin 0 -> 10943 bytes .../zeplin/drawable-xxxhdpi/twinesnap_selected.png | Bin 0 -> 37678 bytes .../Build/Shortcuts/Machine Studio.lnk | Bin 1581 -> 1516 bytes .../Modules/MachineStudio.Dispensers/App.config | 26 +- .../Tango.MachineStudio.ColorCapture/App.config | 26 +- .../Models/CaptureConfig.cs | 7 + .../ViewModels/MainViewVM.cs | 12 + .../Views/MainView.xaml | 9 + .../Tango.MachineStudio.ColorLab/app.config | 26 +- .../Modules/Tango.MachineStudio.DB/App.config | 26 +- .../Tango.MachineStudio.DataCapture/app.config | 24 +- .../Tango.MachineStudio.Developer/app.config | 24 +- .../app.config | 26 +- .../Modules/Tango.MachineStudio.Logging/App.config | 24 +- .../Tango.MachineStudio.MachineDesigner/app.config | 24 +- .../Modules/Tango.MachineStudio.RML/App.config | 26 +- .../Tango.MachineStudio.Statistics/App.config | 26 +- .../Modules/Tango.MachineStudio.Storage/app.config | 26 +- .../Modules/Tango.MachineStudio.Stubs/app.config | 26 +- .../Tango.MachineStudio.Technician/app.config | 26 +- .../Tango.MachineStudio.UsersAndRoles/app.config | 26 +- .../Tango.MachineStudio.Common/app.config | 24 +- .../Tango.MachineStudio.Publisher.CLI/App.config | 26 +- .../Tango.MachineStudio.Publisher.UI/App.config | 24 +- .../Tango.MachineStudio.UI/App.config | 26 +- .../PPC/Modules/Tango.PPC.Events/app.config | 26 +- .../PPC/Modules/Tango.PPC.Jobs/app.config | 24 +- .../Modules/Tango.PPC.MachineSettings/app.config | 24 +- .../PPC/Modules/Tango.PPC.Storage/app.config | 26 +- .../PPC/Modules/Tango.PPC.Technician/app.config | 26 +- .../Visual_Studio/PPC/Tango.PPC.Common/app.config | 24 +- .../PPC/Tango.PPC.Publisher.UI/App.config | 24 +- Software/Visual_Studio/PPC/Tango.PPC.UI/App.config | 92 + .../PPC/Tango.PPC.WatchDog/App.config | 26 +- .../Tango.Scripting.Editors/AvalonEditCommands.cs | 87 + .../CodeCompletion/CompletionList.cs | 377 +++ .../CodeCompletion/CompletionList.xaml | 72 + .../CodeCompletion/CompletionListBox.cs | 103 + .../CodeCompletion/CompletionListBoxItem.cs | 63 + .../CodeCompletion/CompletionWindow.cs | 196 ++ .../CodeCompletion/CompletionWindowBase.cs | 417 +++ .../CodeCompletion/ICompletionData.cs | 49 + .../CodeCompletion/IOverloadProvider.cs | 42 + .../CodeCompletion/InsightWindow.cs | 94 + .../CodeCompletion/InsightWindow.xaml | 118 + .../CodeCompletion/OverloadInsightWindow.cs | 58 + .../CodeCompletion/OverloadViewer.cs | 101 + .../Converters/BooleanToVisibilityConverter.cs | 24 + .../BooleanToVisibilityInversedConverter.cs | 24 + .../Document/ChangeTrackingCheckpoint.cs | 140 + .../Document/DocumentChangeEventArgs.cs | 131 + .../Document/DocumentChangeOperation.cs | 52 + .../Document/DocumentLine.cs | 242 ++ .../Document/DocumentLineTree.cs | 712 +++++ .../Document/GapTextBuffer.cs | 192 ++ .../Document/ILineTracker.cs | 55 + .../Tango.Scripting.Editors/Document/ISegment.cs | 219 ++ .../Document/ITextSource.cs | 320 ++ .../Document/IUndoableOperation.cs | 30 + .../Document/LineManager.cs | 288 ++ .../Tango.Scripting.Editors/Document/LineNode.cs | 83 + .../Document/NewLineFinder.cs | 134 + .../Document/OffsetChangeMap.cs | 347 +++ .../Tango.Scripting.Editors/Document/TextAnchor.cs | 194 ++ .../Document/TextAnchorNode.cs | 87 + .../Document/TextAnchorTree.cs | 753 +++++ .../Document/TextDocument.cs | 836 ++++++ .../Document/TextDocumentWeakEventManager.cs | 149 + .../Document/TextLocation.cs | 166 ++ .../Document/TextSegment.cs | 248 ++ .../Document/TextSegmentCollection.cs | 971 +++++++ .../Document/TextUtilities.cs | 332 +++ .../Document/UndoOperationGroup.cs | 61 + .../Tango.Scripting.Editors/Document/UndoStack.cs | 450 +++ .../Document/WeakLineTracker.cs | 85 + .../Editing/AbstractMargin.cs | 102 + .../Tango.Scripting.Editors/Editing/Caret.cs | 472 +++ .../Tango.Scripting.Editors/Editing/CaretLayer.cs | 86 + .../Editing/CaretNavigationCommandHandler.cs | 375 +++ .../Editing/CaretWeakEventHandler.cs | 33 + .../Editing/DottedLineMargin.cs | 63 + .../Editing/DragDropException.cs | 46 + .../Editing/EditingCommandHandler.cs | 578 ++++ .../Editing/EmptySelection.cs | 85 + .../Editing/IReadOnlySectionProvider.cs | 32 + .../Editing/ImeNativeWrapper.cs | 206 ++ .../Tango.Scripting.Editors/Editing/ImeSupport.cs | 150 + .../Editing/LineNumberMargin.cs | 243 ++ .../Editing/NoReadOnlySections.cs | 50 + .../Editing/RectangleSelection.cs | 396 +++ .../Tango.Scripting.Editors/Editing/Selection.cs | 268 ++ .../Editing/SelectionColorizer.cs | 58 + .../Editing/SelectionLayer.cs | 53 + .../Editing/SelectionMouseHandler.cs | 631 ++++ .../Editing/SelectionSegment.cs | 89 + .../Editing/SimpleSelection.cs | 144 + .../Tango.Scripting.Editors/Editing/TextArea.cs | 1048 +++++++ .../Editing/TextAreaDefaultInputHandlers.cs | 120 + .../Editing/TextAreaInputHandler.cs | 242 ++ .../Editing/TextSegmentReadOnlySectionProvider.cs | 80 + .../Tango.Scripting.Editors/ExtensionMethods.cs | 38 + .../Folding/AbstractFoldingStrategy.cs | 30 + .../Folding/BraceFoldingStrategy.cs | 159 + .../Folding/FoldingElementGenerator.cs | 194 ++ .../Folding/FoldingManager.cs | 388 +++ .../Folding/FoldingMargin.cs | 343 +++ .../Folding/FoldingMarginMarker.cs | 90 + .../Folding/FoldingSection.cs | 186 ++ .../Tango.Scripting.Editors/Folding/NewFolding.cs | 70 + .../Folding/XmlFoldingStrategy.cs | 215 ++ .../Highlighting/DocumentHighlighter.cs | 468 +++ .../Highlighting/HighlightedInlineBuilder.cs | 214 ++ .../Highlighting/HighlightedLine.cs | 154 + .../Highlighting/HighlightedSection.cs | 33 + .../Highlighting/HighlightingBrush.cs | 117 + .../Highlighting/HighlightingColor.cs | 119 + .../Highlighting/HighlightingColorizer.cs | 286 ++ .../HighlightingDefinitionInvalidException.cs | 43 + .../HighlightingDefinitionTypeConverter.cs | 54 + .../Highlighting/HighlightingManager.cs | 290 ++ .../Highlighting/HighlightingRule.cs | 31 + .../Highlighting/HighlightingRuleSet.cs | 46 + .../Highlighting/HighlightingSpan.cs | 64 + .../Highlighting/HtmlClipboard.cs | 201 ++ .../Highlighting/IHighlighter.cs | 35 + .../Highlighting/IHighlightingDefinition.cs | 54 + .../IHighlightingDefinitionReferenceResolver.cs | 18 + .../Highlighting/OffsetColorizer.cs | 39 + .../Highlighting/Resources/ASPX.xshd | 16 + .../Highlighting/Resources/Boo.xshd | 212 ++ .../Highlighting/Resources/CPP-Mode.xshd | 195 ++ .../Highlighting/Resources/CSS-Mode.xshd | 57 + .../Highlighting/Resources/CSharp-Mode.xshd | 311 ++ .../Highlighting/Resources/Coco-Mode.xshd | 74 + .../Highlighting/Resources/HTML-Mode.xshd | 388 +++ .../Highlighting/Resources/Java-Mode.xshd | 152 + .../Highlighting/Resources/JavaScript-Mode.xshd | 132 + .../Highlighting/Resources/MarkDown-Mode.xshd | 56 + .../Highlighting/Resources/ModeV1.xsd | 296 ++ .../Highlighting/Resources/ModeV2.xsd | 167 ++ .../Highlighting/Resources/PHP-Mode.xshd | 158 + .../Highlighting/Resources/Patch-Mode.xshd | 35 + .../Highlighting/Resources/PowerShell.xshd | 146 + .../Highlighting/Resources/Resources.cs | 48 + .../Highlighting/Resources/Tex-Mode.xshd | 108 + .../Highlighting/Resources/VBNET-Mode.xshd | 256 ++ .../Highlighting/Resources/XML-Mode.xshd | 63 + .../Highlighting/Resources/XmlDoc.xshd | 57 + .../Highlighting/Xshd/HighlightingLoader.cs | 99 + .../Highlighting/Xshd/IXshdVisitor.cs | 32 + .../Highlighting/Xshd/SaveXshdVisitor.cs | 182 ++ .../Highlighting/Xshd/V1Loader.cs | 325 +++ .../Highlighting/Xshd/V2Loader.cs | 334 +++ .../Highlighting/Xshd/XmlHighlightingDefinition.cs | 406 +++ .../Highlighting/Xshd/XshdColor.cs | 97 + .../Highlighting/Xshd/XshdElement.cs | 29 + .../Highlighting/Xshd/XshdImport.cs | 25 + .../Highlighting/Xshd/XshdKeywords.cs | 36 + .../Highlighting/Xshd/XshdProperty.cs | 38 + .../Highlighting/Xshd/XshdReference.cs | 127 + .../Highlighting/Xshd/XshdRule.cs | 35 + .../Highlighting/Xshd/XshdRuleSet.cs | 51 + .../Highlighting/Xshd/XshdSpan.cs | 82 + .../Highlighting/Xshd/XshdSyntaxDefinition.cs | 50 + .../Tango.Scripting.Editors/Images/class.png | Bin 0 -> 417 bytes .../Tango.Scripting.Editors/Images/enum.png | Bin 0 -> 190 bytes .../Tango.Scripting.Editors/Images/field.png | Bin 0 -> 216 bytes .../Tango.Scripting.Editors/Images/interface.png | Bin 0 -> 381 bytes .../Tango.Scripting.Editors/Images/method.png | Bin 0 -> 276 bytes .../Tango.Scripting.Editors/Images/namespace.png | Bin 0 -> 241 bytes .../Tango.Scripting.Editors/Images/property.png | Bin 0 -> 428 bytes .../Tango.Scripting.Editors/Images/struct.png | Bin 0 -> 136 bytes .../Indentation/CSharp/CSharpIndentationHelper.cs | 124 + .../CSharp/CSharpIndentationStrategy.cs | 96 + .../Indentation/CSharp/DocumentAccessor.cs | 107 + .../Indentation/CSharp/IndentationReformatter.cs | 474 +++ .../Indentation/DefaultIndentationStrategy.cs | 40 + .../Indentation/IIndentationStrategy.cs | 25 + .../Intellisense/ClassCompletionItem.cs | 32 + .../Intellisense/ClassCompletionItemPopup.cs | 25 + .../Intellisense/CompletionItem.cs | 49 + .../Intellisense/CompletionItemPopupControl.cs | 14 + .../Intellisense/EnumCompletionItem.cs | 21 + .../Intellisense/EnumCompletionItemPopup.cs | 25 + .../Intellisense/FieldCompletionItem.cs | 20 + .../Intellisense/FieldCompletionItemPopup.cs | 25 + .../Intellisense/ICompletionItem.cs | 14 + .../Intellisense/ICompletionProvider.cs | 13 + .../Intellisense/InterfaceCompletionItem.cs | 21 + .../Intellisense/InterfaceCompletionItemPopup.cs | 25 + .../Intellisense/KnownType.cs | 222 ++ .../Intellisense/KnownTypeConstructor.cs | 28 + .../Intellisense/KnownTypeField.cs | 21 + .../Intellisense/KnownTypeMember.cs | 31 + .../Intellisense/KnownTypeMethod.cs | 42 + .../Intellisense/KnownTypeMethodParameter.cs | 16 + .../Intellisense/KnownTypeProperty.cs | 21 + .../Intellisense/MethodCompletionItem.cs | 47 + .../Intellisense/MethodCompletionItemPopup.cs | 25 + .../Intellisense/NamespaceCompletionItem.cs | 27 + .../Intellisense/NamespaceCompletionItemPopup.cs | 25 + .../Intellisense/PropertyCompletionItem.cs | 22 + .../Intellisense/PropertyCompletionItemPopup.cs | 25 + .../Intellisense/StructCompletionItem.cs | 21 + .../Intellisense/StructCompletionItemPopup.cs | 25 + .../Tango.Scripting.Editors/Intellisense/Utils.cs | 171 ++ .../Popups/MethodDescription.cs | 22 + .../Tango.Scripting.Editors/Popups/MethodPopup.cs | 82 + .../Popups/ParameterDescription.cs | 26 + .../Properties/AssemblyInfo.cs | 40 + .../Properties/CodeAnalysisDictionary.xml | 33 + .../Rendering/BackgroundGeometryBuilder.cs | 343 +++ .../Rendering/CollapsedLineSection.cs | 95 + .../Rendering/ColorizingTransformer.cs | 108 + .../Rendering/ColumnRulerRenderer.cs | 64 + .../DefaultTextRunTypographyProperties.cs | 171 ++ .../Rendering/DocumentColorizingTransformer.cs | 81 + .../Rendering/FormattedTextElement.cs | 207 ++ .../Rendering/GlobalTextRunProperties.cs | 30 + .../Rendering/HeightTree.cs | 1092 +++++++ .../Rendering/HeightTreeLineNode.cs | 49 + .../Rendering/HeightTreeNode.cs | 155 + .../Rendering/IBackgroundRenderer.cs | 26 + .../Rendering/ITextRunConstructionContext.cs | 47 + .../Rendering/ITextViewConnect.cs | 24 + .../Rendering/IVisualLineTransformer.cs | 19 + .../Rendering/InlineObjectRun.cs | 145 + .../Tango.Scripting.Editors/Rendering/Layer.cs | 43 + .../Rendering/LayerPosition.cs | 91 + .../Rendering/LinkElementGenerator.cs | 144 + .../Rendering/MouseHoverLogic.cs | 134 + .../Rendering/SimpleTextSource.cs | 39 + .../Rendering/SingleCharacterElementGenerator.cs | 268 ++ .../Tango.Scripting.Editors/Rendering/TextLayer.cs | 70 + .../Tango.Scripting.Editors/Rendering/TextView.cs | 2009 +++++++++++++ .../Rendering/TextViewCachedElements.cs | 43 + .../Rendering/TextViewWeakEventManager.cs | 71 + .../Rendering/VisualLine.cs | 681 +++++ .../VisualLineConstructionStartEventArgs.cs | 29 + .../Rendering/VisualLineElement.cs | 249 ++ .../Rendering/VisualLineElementGenerator.cs | 63 + .../VisualLineElementTextRunProperties.cs | 241 ++ .../Rendering/VisualLineLinkText.cs | 114 + .../Rendering/VisualLineText.cs | 122 + .../Rendering/VisualLineTextParagraphProperties.cs | 31 + .../Rendering/VisualLineTextSource.cs | 123 + .../Rendering/VisualLinesInvalidException.cs | 44 + .../Rendering/VisualYPosition.cs | 46 + .../Tango.Scripting.Editors/ScriptEditor.cs | 1595 ++++++++++ .../Search/DropDownButton.cs | 60 + .../Search/DropDownButton.xaml | 71 + .../Search/ISearchStrategy.cs | 88 + .../Tango.Scripting.Editors/Search/Localization.cs | 64 + .../Search/RegexSearchStrategy.cs | 70 + .../Search/SearchCommands.cs | 105 + .../Tango.Scripting.Editors/Search/SearchPanel.cs | 467 +++ .../Search/SearchPanel.xaml | 47 + .../Search/SearchResultBackgroundRenderer.cs | 78 + .../Search/SearchStrategyFactory.cs | 68 + .../Tango.Scripting.Editors/Search/next.png | Bin 0 -> 1304 bytes .../Tango.Scripting.Editors/Search/prev.png | Bin 0 -> 1300 bytes .../Snippets/IActiveElement.cs | 35 + .../Snippets/InsertionContext.cs | 269 ++ .../Tango.Scripting.Editors/Snippets/Snippet.cs | 47 + .../Snippets/SnippetAnchorElement.cs | 97 + .../Snippets/SnippetBoundElement.cs | 122 + .../Snippets/SnippetCaretElement.cs | 58 + .../Snippets/SnippetContainerElement.cs | 49 + .../Snippets/SnippetElement.cs | 32 + .../Snippets/SnippetEventArgs.cs | 57 + .../Snippets/SnippetInputHandler.cs | 80 + .../Snippets/SnippetReplaceableTextElement.cs | 213 ++ .../Snippets/SnippetSelectionElement.cs | 45 + .../Snippets/SnippetTextElement.cs | 39 + .../Tango.Scripting.Editors.csproj | 634 ++++ .../Tango.Scripting.Editors/TextEditor.cs | 1144 ++++++++ .../Tango.Scripting.Editors/TextEditor.xaml | 79 + .../TextEditorAutomationPeer.cs | 65 + .../Tango.Scripting.Editors/TextEditorComponent.cs | 40 + .../Tango.Scripting.Editors/TextEditorOptions.cs | 434 +++ .../TextEditorWeakEventManager.cs | 52 + .../Tango.Scripting.Editors/TextViewPosition.cs | 158 + .../Scripting/Tango.Scripting.Editors/Theme.xaml | 3066 ++++++++++++++++++++ .../Tango.Scripting.Editors/Themes/Generic.xaml | 570 ++++ .../Tango.Scripting.Editors/Themes/RightArrow.cur | Bin 0 -> 326 bytes .../Tango.Scripting.Editors/Utils/Boxes.cs | 21 + .../Tango.Scripting.Editors/Utils/BusyManager.cs | 55 + .../Utils/CallbackOnDispose.cs | 35 + .../Tango.Scripting.Editors/Utils/CharRope.cs | 172 ++ .../Utils/CompressingTreeList.cs | 882 ++++++ .../Tango.Scripting.Editors/Utils/Constants.cs | 15 + .../Tango.Scripting.Editors/Utils/DelayedEvents.cs | 48 + .../Tango.Scripting.Editors/Utils/Deque.cs | 174 ++ .../Tango.Scripting.Editors/Utils/Empty.cs | 17 + .../Utils/ExtensionMethods.cs | 214 ++ .../Tango.Scripting.Editors/Utils/FileReader.cs | 208 ++ .../Utils/ImmutableStack.cs | 114 + .../Utils/NullSafeCollection.cs | 31 + .../Utils/ObserveAddRemoveCollection.cs | 63 + .../Utils/PixelSnapHelpers.cs | 92 + .../Utils/PropertyChangedWeakEventManager.cs | 26 + .../Tango.Scripting.Editors/Utils/Rope.cs | 789 +++++ .../Tango.Scripting.Editors/Utils/RopeNode.cs | 605 ++++ .../Utils/RopeTextReader.cs | 105 + .../Tango.Scripting.Editors/Utils/StringSegment.cs | 107 + .../Utils/TextFormatterFactory.cs | 71 + .../Tango.Scripting.Editors/Utils/ThrowUtil.cs | 56 + .../Utils/WeakEventManagerBase.cs | 86 + .../Tango.Scripting.Editors/Utils/Win32.cs | 85 + .../Tango.Scripting.Editors/Xml/AXmlAttribute.cs | 129 + .../Xml/AXmlAttributeCollection.cs | 119 + .../Tango.Scripting.Editors/Xml/AXmlContainer.cs | 282 ++ .../Tango.Scripting.Editors/Xml/AXmlDocument.cs | 69 + .../Tango.Scripting.Editors/Xml/AXmlElement.cs | 226 ++ .../Tango.Scripting.Editors/Xml/AXmlObject.cs | 266 ++ .../Xml/AXmlObjectCollection.cs | 90 + .../Xml/AXmlObjectEventArgs.cs | 21 + .../Tango.Scripting.Editors/Xml/AXmlParser.cs | 201 ++ .../Tango.Scripting.Editors/Xml/AXmlTag.cs | 108 + .../Tango.Scripting.Editors/Xml/AXmlText.cs | 62 + .../Xml/AbstractAXmlVisitor.cs | 44 + .../Xml/CanonicalPrintAXmlVisitor.cs | 119 + .../Xml/ExtensionMethods.cs | 47 + .../Xml/FilteredCollection.cs | 99 + .../Tango.Scripting.Editors/Xml/IAXmlVisitor.cs | 29 + .../Xml/InternalException.cs | 45 + .../Xml/MergedCollection.cs | 70 + .../Xml/PrettyPrintAXmlVisitor.cs | 69 + .../Tango.Scripting.Editors/Xml/SyntaxError.cs | 36 + .../Xml/TagMatchingHeuristics.cs | 439 +++ .../Tango.Scripting.Editors/Xml/TagReader.cs | 740 +++++ .../Tango.Scripting.Editors/Xml/TextType.cs | 39 + .../Tango.Scripting.Editors/Xml/TokenReader.cs | 309 ++ .../Xml/TrackedSegmentCollection.cs | 165 ++ .../Scripting/Tango.Scripting.Editors/app.config | 83 + .../Tango.Scripting.Editors/packages.config | 47 + .../Scripting/Tango.Scripting.IDE.UI/App.config | 55 + .../Scripting/Tango.Scripting.IDE.UI/App.xaml | 27 + .../Scripting/Tango.Scripting.IDE.UI/App.xaml.cs | 17 + .../Tango.Scripting.IDE.UI/MainWindow.xaml | 14 + .../Tango.Scripting.IDE.UI/MainWindow.xaml.cs | 30 + .../Tango.Scripting.IDE.UI/MainWindowVM.cs | 19 + .../Properties/AssemblyInfo.cs | 19 + .../Properties/Resources.Designer.cs | 71 + .../Properties/Resources.resx | 117 + .../Properties/Settings.Designer.cs | 30 + .../Properties/Settings.settings | 7 + .../Tango.Scripting.IDE.UI.csproj | 208 ++ .../Tango.Scripting.IDE.UI/packages.config | 49 + .../Tango.Scripting.IDE/Controls/ErrorData.cs | 54 + .../Controls/SharedResourceDictionary.cs | 55 + .../Controls/SolutionItemControl.cs | 52 + .../Tango.Scripting.IDE/Controls/TabConrolClose.cs | 26 + .../Converters/LeftMarginMultiplierConverter.cs | 54 + .../Dialogs/AddProjectView.xaml | 130 + .../Dialogs/AddProjectView.xaml.cs | 28 + .../Dialogs/AddProjectViewVM.cs | 18 + .../Dialogs/BaseProjectDialogVM.cs | 107 + .../Dialogs/NewProjectView.xaml | 139 + .../Dialogs/NewProjectView.xaml.cs | 29 + .../Dialogs/NewProjectViewVM.cs | 70 + .../Tango.Scripting.IDE/IDEDialogViewModel.cs | 14 + .../Scripting/Tango.Scripting.IDE/IDESettings.cs | 19 + .../Scripting/Tango.Scripting.IDE/IDEViewModel.cs | 25 + .../Scripting/Tango.Scripting.IDE/IProject.cs | 23 + .../Scripting/Tango.Scripting.IDE/IProjectItem.cs | 18 + .../Scripting/Tango.Scripting.IDE/IProjectType.cs | 22 + .../Scripting/Tango.Scripting.IDE/ISolutionItem.cs | 18 + .../Tango.Scripting.IDE/Images/CSharpProject.png | Bin 0 -> 200 bytes .../Images/CSharpScriptItem.png | Bin 0 -> 238 bytes .../Images/CloseDocument_16x.png | Bin 0 -> 273 bytes .../Images/CloseSolution_16x.png | Bin 0 -> 279 bytes .../Tango.Scripting.IDE/Images/FindinFiles_16x.png | Bin 0 -> 420 bytes .../Images/NewFileCollection_16x.png | Bin 0 -> 267 bytes .../Images/NewRelationship_16x.png | Bin 0 -> 248 bytes .../Tango.Scripting.IDE/Images/NewWindow_16x.png | Bin 0 -> 217 bytes .../Tango.Scripting.IDE/Images/OpenFolder_16x.png | Bin 0 -> 336 bytes .../Tango.Scripting.IDE/Images/Pause_16x.png | Bin 0 -> 218 bytes .../Tango.Scripting.IDE/Images/Redo_16x.png | Bin 0 -> 229 bytes .../Tango.Scripting.IDE/Images/Reference.png | Bin 0 -> 141 bytes .../Tango.Scripting.IDE/Images/SaveAll_16x.png | Bin 0 -> 268 bytes .../Tango.Scripting.IDE/Images/SaveClose_16x.png | Bin 0 -> 343 bytes .../Images/SaveStatusBar9_16x.png | Bin 0 -> 239 bytes .../Tango.Scripting.IDE/Images/Save_16x.png | Bin 0 -> 225 bytes .../Tango.Scripting.IDE/Images/StubProject.png | Bin 0 -> 392 bytes .../Tango.Scripting.IDE/Images/Undo_16x.png | Bin 0 -> 237 bytes .../Tango.Scripting.IDE/Images/algorithm.png | Bin 0 -> 1664 bytes .../Images/checklist_white_32.png | Bin 0 -> 1294 bytes .../Tango.Scripting.IDE/Images/clipboard.png | Bin 0 -> 1028 bytes .../Tango.Scripting.IDE/Images/pp_project.png | Bin 0 -> 757 bytes .../Images/redo-arrow-symbol.png | Bin 0 -> 525 bytes .../Images/redo-arrow-symbol1.png | Bin 0 -> 491 bytes .../Scripting/Tango.Scripting.IDE/Images/stop.png | Bin 0 -> 183 bytes .../Images/stub_project_126.png | Bin 0 -> 4406 bytes .../Tango.Scripting.IDE/Images/stub_project_32.png | Bin 0 -> 1793 bytes .../Images/stub_project_whit_32.png | Bin 0 -> 1269 bytes .../Scripting/Tango.Scripting.IDE/Images/test.png | Bin 0 -> 1060 bytes .../Tango.Scripting.IDE/Images/unitTest.png | Bin 0 -> 1965 bytes .../Tango.Scripting.IDE/Images/unitTest_126.png | Bin 0 -> 4060 bytes .../Notifications/DefaultNotificationManager.cs | 94 + .../Notifications/INotificationManager.cs | 95 + .../Notifications/ProgressNotificationHandler.cs | 23 + .../Scripting/Tango.Scripting.IDE/Project.cs | 56 + .../Scripting/Tango.Scripting.IDE/ProjectItem.cs | 57 + .../ProjectItems/CSharpScriptItem.cs | 30 + .../ProjectItems/ReferenceAssembliesItem.cs | 27 + .../ProjectItems/ReferenceAssemblyItem.cs | 24 + .../ProjectItemsViews/CSharpScriptItemView.xaml | 13 + .../ProjectItemsViews/CSharpScriptItemView.xaml.cs | 29 + .../Scripting/Tango.Scripting.IDE/ProjectType.cs | 42 + .../ProjectTypes/StubProjectType.cs | 48 + .../ProjectTypes/UnitTestProjectType.cs | 46 + .../Tango.Scripting.IDE/Projects/CSharpProject.cs | 14 + .../Tango.Scripting.IDE/Projects/StubProject.cs | 25 + .../Projects/UnitTestProject.cs | 24 + .../Tango.Scripting.IDE/Properties/AssemblyInfo.cs | 19 + .../Properties/Resources.Designer.cs | 62 + .../Tango.Scripting.IDE/Properties/Resources.resx | 117 + .../Properties/Settings.Designer.cs | 30 + .../Properties/Settings.settings | 7 + .../Scripting/Tango.Scripting.IDE/Resources.xaml | 127 + .../Tango.Scripting.IDE/ScriptIDEView.xaml | 148 + .../Tango.Scripting.IDE/ScriptIDEView.xaml.cs | 28 + .../Tango.Scripting.IDE/ScriptIDEView2.xaml | 271 ++ .../Tango.Scripting.IDE/ScriptIDEView2.xaml.cs | 93 + .../Tango.Scripting.IDE/ScriptIDEViewVM.cs | 191 ++ .../Scripting/Tango.Scripting.IDE/Solution.cs | 21 + .../Tango.Scripting.IDE/Tango.Scripting.IDE.csproj | 308 ++ .../Tango.Scripting.IDE/Themes/ButtonStyle.xaml | 81 + .../Tango.Scripting.IDE/Themes/ComboboxStyle.xaml | 200 ++ .../Themes/DarkThemesColors.xaml | 100 + .../Tango.Scripting.IDE/Themes/DataGridStyle.xaml | 248 ++ .../Tango.Scripting.IDE/Themes/Generic.xaml | 36 + .../Themes/LightThemesColors.xaml | 15 + .../Tango.Scripting.IDE/Themes/MenuDict.xaml | 229 ++ .../Themes/ScrollViewerStyle.xaml | 258 ++ .../Tango.Scripting.IDE/Themes/Shared.xaml | 14 + .../Tango.Scripting.IDE/Themes/TabConrolStyle.xaml | 354 +++ .../Tango.Scripting.IDE/Themes/ToolbarStyle.xaml | 236 ++ .../Tango.Scripting.IDE/Themes/TreeViewItem.xaml | 177 ++ .../Tango.Scripting.IDE/ViewModelLocator.cs | 26 + .../Tango.Scripting.IDE/Windows/DialogWindow.xaml | 28 + .../Windows/DialogWindow.xaml.cs | 28 + .../Scripting/Tango.Scripting.IDE/app.config | 51 + .../Scripting/Tango.Scripting.IDE/packages.config | 7 + .../Scripting/Tango.Scripting/CompilationError.cs | 28 + .../Tango.Scripting/CompilationException.cs | 28 + .../Scripting/Tango.Scripting/ExtensionMethods.cs | 18 + .../Scripting/Tango.Scripting/GlobalObject.cs | 21 + .../Scripting/Tango.Scripting/IScriptSession.cs | 13 + .../Scripting/Tango.Scripting/IScriptingEngine.cs | 14 + .../Tango.Scripting/Parsing/ScriptParser.cs | 382 +++ .../Tango.Scripting/Parsing/ScriptSymbol.cs | 31 + .../Tango.Scripting/Parsing/ScriptType.cs | 23 + .../Tango.Scripting/Properties/AssemblyInfo.cs | 7 + .../Scripting/Tango.Scripting/ReferenceAssembly.cs | 45 + .../Scripting/Tango.Scripting/Script.cs | 41 + .../Scripting/Tango.Scripting/ScriptSession.cs | 58 + .../Tango.Scripting/ScriptSessionState.cs | 16 + .../ScriptSessionStateChangedEventArgs.cs | 17 + .../Scripting/Tango.Scripting/ScriptingEngine.cs | 268 ++ .../Tango.Scripting/Tango.Scripting.csproj | 154 + .../Scripting/Tango.Scripting/app.config | 83 + .../Scripting/Tango.Scripting/packages.config | 51 + .../Visual_Studio/Scripting/TestApp/App.config | 46 + Software/Visual_Studio/Scripting/TestApp/App.xaml | 9 + .../Visual_Studio/Scripting/TestApp/App.xaml.cs | 17 + .../Scripting/TestApp/MainWindow.xaml | 14 + .../Scripting/TestApp/MainWindow.xaml.cs | 77 + .../Scripting/TestApp/Properties/AssemblyInfo.cs | 55 + .../TestApp/Properties/Resources.Designer.cs | 71 + .../Scripting/TestApp/Properties/Resources.resx | 117 + .../TestApp/Properties/Settings.Designer.cs | 30 + .../Scripting/TestApp/Properties/Settings.settings | 7 + .../Visual_Studio/Scripting/TestApp/TestApp.csproj | 122 + .../Scripting/TestApp/packages.config | 5 + .../TCC/Tango.TCC.BL/CardDetectionConfig.cs | 1 + .../Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs | 1 + .../TCC/Tango.TCC.BL/Web/DefinitionResponse.cs | 2 + .../TCC/Tango.TCC.CardDetector/CardDetection.cpp | Bin 10128 -> 10390 bytes .../Tango.TCC.CardDetector/CardDetectionConfig.h | 1 + .../Controllers/ColorDetectionController.cs | 1 + .../TCC/Tango.TCC.Service/TCCServiceConfig.cs | 5 + .../Visual_Studio/TCC/Tango.TCC.Service/Web.config | 1 + .../Dialogs/CommonOpenFileDialog.cs | 7 - .../Tango.Scripting.IDE/IDESettings.cs | 12 + .../Tango.Scripting.IDE/ProjectType.cs | 1 + .../ProjectTypes/StubProjectType.cs | 3 +- .../ProjectTypes/UnitTestProjectType.cs | 3 +- .../Tango.Scripting.IDE/ScriptIDEViewVM.cs | 15 +- .../Tango.Scripting.IDE/Solution.cs | 2 + .../Tango.Scripting.IDE/Tango.Scripting.IDE.csproj | 2 +- .../Tango.Scripting.IDE/Windows/DialogWindow.xaml | 4 +- .../Visual_Studio/Tango.EmbroideryUI/app.config | 24 +- Software/Visual_Studio/Tango.Explorer/app.config | 26 +- .../Visual_Studio/Tango.SharedUI/DialogViewVM.cs | 10 +- Software/Visual_Studio/Tango.SharedUI/app.config | 22 +- Software/Visual_Studio/Tango.Stubs/app.config | 24 +- Software/Visual_Studio/Tango.Touch/app.config | 24 +- .../Visual_Studio/Tango.UnitTesting/App.config | 26 +- Software/Visual_Studio/Tango.sln | 186 +- .../Utilities/Tango.EmbroideryViewer/App.config | 24 +- .../Tango.FirmwarePackageGenerator/App.config | 26 +- .../Utilities/Tango.ILMerge.UI/App.config | 24 +- .../Utilities/Tango.MachineEM.UI/App.config | 24 +- .../Utilities/Tango.Protobuf.UI/App.config | 24 +- .../Utilities/Tango.RemoteRunner.UI/App.config | 24 +- .../Utilities/Tango.Stubs.UI/App.config | 26 +- .../Utilities/Tango.TransportRouter.UI/App.config | 24 +- .../Utilities/Tango.UITests/App.config | 24 +- .../Utilities/Tango.WebClientGenerator/App.config | 26 +- .../Web/Tango.MachineService/Web.config | 28 +- 649 files changed, 60376 insertions(+), 507 deletions(-) create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/controls/ToggleImageButton.java create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingAdapters.java create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingConverters.java create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/my_colors.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twine_logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/my_colors.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twine_logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/my_colors.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twine_logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/my_colors.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twine_logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/my_colors.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twine_logo.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_normal.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_selected.png create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable/background_gradient.xml create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border.xml create mode 100644 Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border_shadow.xml create mode 100644 Software/Android_Studio/settings.jar create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_aruco330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_aruco330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_calib3d330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_calib3d330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_core330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_core330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_dnn330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_dnn330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_features2d330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_features2d330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_flann330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_flann330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_highgui330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_highgui330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_imgcodecs330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_imgcodecs330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_imgproc330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_imgproc330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_ml330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_ml330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_objdetect330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_objdetect330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_photo330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_photo330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_shape330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_shape330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_stitching330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_stitching330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_superres330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_superres330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_ts330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_video330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_video330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_videoio330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_videoio330d.lib create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_videostab330d.exp create mode 100644 Software/External_Repositories/OpenCV/bin/opencv_videostab330d.lib create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/logo.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/more_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/more_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/my_colors.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/twinesnap_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-hdpi/twinesnap_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/logo.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/more_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/more_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/my_colors.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/twinesnap_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-mdpi/twinesnap_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/logo.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/more_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/more_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/my_colors.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/twinesnap_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xhdpi/twinesnap_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/logo.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/more_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/more_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/my_colors.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/twinesnap_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxhdpi/twinesnap_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/logo.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/more_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/more_selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/my_colors.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/selected.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/twinesnap_normal.png create mode 100644 Software/Graphics/Mobile/zeplin/drawable-xxxhdpi/twinesnap_selected.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/AvalonEditCommands.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBox.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBoxItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindow.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/ICompletionData.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/IOverloadProvider.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityConverter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityInversedConverter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ChangeTrackingCheckpoint.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeOperation.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLineTree.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/GapTextBuffer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ILineTracker.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ISegment.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ITextSource.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/IUndoableOperation.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineNode.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/NewLineFinder.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/OffsetChangeMap.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorNode.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorTree.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocumentWeakEventManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextLocation.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegment.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextUtilities.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoOperationGroup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/WeakLineTracker.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/DocumentHighlighter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedInlineBuilder.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedLine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedSection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingBrush.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColorizer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionInvalidException.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionTypeConverter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRule.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRuleSet.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingSpan.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HtmlClipboard.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlighter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinition.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinitionReferenceResolver.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ASPX.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Boo.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CPP-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSS-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Coco-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/HTML-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Java-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/JavaScript-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/MarkDown-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV1.xsd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV2.xsd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PHP-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Patch-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PowerShell.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Resources.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Tex-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/VBNET-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XML-Mode.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XmlDoc.xshd create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/HighlightingLoader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/IXshdVisitor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/SaveXshdVisitor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/V1Loader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/V2Loader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XmlHighlightingDefinition.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdColor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdImport.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdKeywords.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdProperty.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdReference.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRule.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRuleSet.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSpan.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSyntaxDefinition.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/class.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/enum.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/field.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/interface.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/method.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/namespace.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/property.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/struct.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationHelper.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/DocumentAccessor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/IndentationReformatter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/DefaultIndentationStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/IIndentationStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItemPopupControl.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionProvider.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeConstructor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeField.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMember.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethod.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethodParameter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeProperty.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItemPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/Utils.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodDescription.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodPopup.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/ParameterDescription.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/CodeAnalysisDictionary.xml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/BackgroundGeometryBuilder.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/CollapsedLineSection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColorizingTransformer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColumnRulerRenderer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DefaultTextRunTypographyProperties.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DocumentColorizingTransformer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/FormattedTextElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/GlobalTextRunProperties.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTree.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeLineNode.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeNode.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IBackgroundRenderer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextRunConstructionContext.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextViewConnect.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IVisualLineTransformer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/InlineObjectRun.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/Layer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LayerPosition.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LinkElementGenerator.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/MouseHoverLogic.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SimpleTextSource.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SingleCharacterElementGenerator.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextLayer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextView.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewCachedElements.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewWeakEventManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineConstructionStartEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementGenerator.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementTextRunProperties.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineLinkText.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineText.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextParagraphProperties.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextSource.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLinesInvalidException.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualYPosition.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorAutomationPeer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorComponent.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorOptions.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorWeakEventManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextViewPosition.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Theme.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/Generic.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/RightArrow.cur create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Boxes.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/BusyManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CallbackOnDispose.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CharRope.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CompressingTreeList.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Constants.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/DelayedEvents.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Deque.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Empty.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ExtensionMethods.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/FileReader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ImmutableStack.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/NullSafeCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ObserveAddRemoveCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PixelSnapHelpers.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PropertyChangedWeakEventManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Rope.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeNode.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeTextReader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/StringSegment.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/TextFormatterFactory.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ThrowUtil.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/WeakEventManagerBase.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Win32.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlText.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AbstractAXmlVisitor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/CanonicalPrintAXmlVisitor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/ExtensionMethods.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/FilteredCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/IAXmlVisitor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/InternalException.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/MergedCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/PrettyPrintAXmlVisitor.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/SyntaxError.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagMatchingHeuristics.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagReader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindowVM.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.Designer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.resx create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.Designer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.settings create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Tango.Scripting.IDE.UI.csproj create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/packages.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/ErrorData.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SharedResourceDictionary.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SolutionItemControl.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/TabConrolClose.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Converters/LeftMarginMultiplierConverter.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectViewVM.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/BaseProjectDialogVM.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectViewVM.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEDialogViewModel.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDESettings.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEViewModel.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ISolutionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpProject.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpScriptItem.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseDocument_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseSolution_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/FindinFiles_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewFileCollection_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewRelationship_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewWindow_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/OpenFolder_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Pause_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Redo_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Reference.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveAll_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveClose_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveStatusBar9_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Save_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/StubProject.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Undo_16x.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/algorithm.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/checklist_white_32.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/clipboard.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/pp_project.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol1.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stop.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_126.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_32.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_whit_32.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/test.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest_126.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/DefaultNotificationManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/INotificationManager.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/ProgressNotificationHandler.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Project.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/CSharpScriptItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssembliesItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssemblyItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/CSharpProject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/StubProject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/UnitTestProject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.Designer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.resx create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.Designer.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.settings create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Resources.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Solution.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ButtonStyle.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ComboboxStyle.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DarkThemesColors.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DataGridStyle.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Generic.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/LightThemesColors.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/MenuDict.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ScrollViewerStyle.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Shared.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TabConrolStyle.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ToolbarStyle.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TreeViewItem.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ViewModelLocator.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/app.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.IDE/packages.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/app.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/packages.config create mode 100644 Software/Visual_Studio/Scripting/TestApp/App.config create mode 100644 Software/Visual_Studio/Scripting/TestApp/App.xaml create mode 100644 Software/Visual_Studio/Scripting/TestApp/App.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml create mode 100644 Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml.cs create mode 100644 Software/Visual_Studio/Scripting/TestApp/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Scripting/TestApp/Properties/Resources.Designer.cs create mode 100644 Software/Visual_Studio/Scripting/TestApp/Properties/Resources.resx create mode 100644 Software/Visual_Studio/Scripting/TestApp/Properties/Settings.Designer.cs create mode 100644 Software/Visual_Studio/Scripting/TestApp/Properties/Settings.settings create mode 100644 Software/Visual_Studio/Scripting/TestApp/TestApp.csproj create mode 100644 Software/Visual_Studio/Scripting/TestApp/packages.config delete mode 100644 Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Dialogs/CommonOpenFileDialog.cs create mode 100644 Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/IDESettings.cs (limited to 'Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common') diff --git a/.gitignore b/.gitignore index 041656ff8..f17e6ea57 100644 --- a/.gitignore +++ b/.gitignore @@ -308,3 +308,6 @@ __pycache__/ # ZBar binaries !/Software/External_Repositories/ZBar/bin + +# OpenCV binaries +!/Software/External_Repositories/OpenCV/bin diff --git a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/controls/ToggleImageButton.java b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/controls/ToggleImageButton.java new file mode 100644 index 000000000..466b6de7e --- /dev/null +++ b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/controls/ToggleImageButton.java @@ -0,0 +1,122 @@ +package com.twine.colorcapture.controls; + +import android.content.Context; +import android.content.res.TypedArray; +import android.databinding.BindingAdapter; +import android.databinding.BindingMethod; +import android.databinding.InverseBindingAdapter; +import android.databinding.InverseBindingListener; +import android.databinding.InverseBindingMethod; +import android.databinding.InverseBindingMethods; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.Checkable; + +import com.twine.colorcapture.R; + +public class ToggleImageButton extends android.support.v7.widget.AppCompatImageButton implements Checkable +{ + private OnCheckedChangeListener onCheckedChangeListener; + private Drawable normalImage; + private Drawable checkedImage; + + public ToggleImageButton(Context context) + { + super(context); + } + + public ToggleImageButton(Context context, AttributeSet attrs) + { + super(context, attrs); + setAttributes(attrs); + } + + public ToggleImageButton(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + setAttributes(attrs); + } + + private void setAttributes(AttributeSet attrs) + { + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ToggleImageButton); + + normalImage = getContext().getDrawable(a.getResourceId(R.styleable.ToggleImageButton_normalImage, 0)); + checkedImage = getContext().getDrawable(a.getResourceId(R.styleable.ToggleImageButton_checkedImage, 0)); + + setChecked(a.getBoolean(R.styleable.ToggleImageButton_android_checked, false)); + a.recycle(); + } + + @Override + public boolean isChecked() + { + return isSelected(); + } + + @Override + public void setChecked(boolean checked) + { + setSelected(checked); + + if (checked) + { + setImageDrawable(checkedImage); + } + else + { + setImageDrawable(normalImage); + } + + if (onCheckedChangeListener != null) + { + onCheckedChangeListener.onCheckedChanged(this, checked); + } + } + + @Override + public void toggle() + { + setChecked(!isChecked()); + } + + @Override + public boolean performClick() + { + toggle(); + return super.performClick(); + } + + public OnCheckedChangeListener getOnCheckedChangeListener() + { + return onCheckedChangeListener; + } + + public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) + { + this.onCheckedChangeListener = onCheckedChangeListener; + } + + public static interface OnCheckedChangeListener + { + public void onCheckedChanged(ToggleImageButton buttonView, boolean isChecked); + } + + @BindingAdapter(value = "cccAttrChanged") + public static void createCheckedBinding(ToggleImageButton btn, InverseBindingListener listener) + { + btn.setOnCheckedChangeListener((e, r) -> listener.onChange()); + } + + @BindingAdapter("ccc") + public static void setChecked(ToggleImageButton view, boolean value) + { + view.setChecked(value); + } + + @InverseBindingAdapter(attribute = "ccc") + public static boolean getChecked(ToggleImageButton view) + { + return view.isChecked(); + } +} diff --git a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingAdapters.java b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingAdapters.java new file mode 100644 index 000000000..41bfb052b --- /dev/null +++ b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingAdapters.java @@ -0,0 +1,9 @@ +package com.twine.colorcapture.mvvm; + +import android.databinding.BindingConversion; +import android.databinding.InverseBindingMethod; + +public class BindingAdapters +{ + +} diff --git a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingConverters.java b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingConverters.java new file mode 100644 index 000000000..929d1ee0e --- /dev/null +++ b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/mvvm/BindingConverters.java @@ -0,0 +1,17 @@ +package com.twine.colorcapture.mvvm; + +import android.databinding.BindingConversion; +import android.databinding.InverseBindingMethod; + +public class BindingConverters +{ +// @BindingConversion +// public static boolean toInt(Boolean value) { +// return value; +// } +// +// @InverseBindingMethod() +// public static int toObject(int number) { +// return number; +// } +} diff --git a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/home/HomeFragment.java b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/home/HomeFragment.java index 76bdfe20f..4dc05b81d 100644 --- a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/home/HomeFragment.java +++ b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/home/HomeFragment.java @@ -1,4 +1,5 @@ package com.twine.colorcapture.views.home; + import android.Manifest; import android.content.pm.PackageManager; import android.os.Build; @@ -15,37 +16,39 @@ import com.twine.colorcapture.mvvm.FragmentBase; public class HomeFragment extends FragmentBase implements IHomeFragment { private IAction1 cameraAccessAction; - + public HomeFragment() { // Required empty public constructor } - + @Override protected int getLayoutId() { return R.layout.fragment_home; } - + @Override protected void inject() { App.getComponent().inject(this); } - + @Override public String getTitle() { return "Home"; } - + @Override public void requestCameraAccess(IAction1 action) { cameraAccessAction = action; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - if (ContextCompat.checkSelfPermission(this.getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + if (ContextCompat.checkSelfPermission(this.getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) + { ActivityCompat.requestPermissions(this.getActivity(), new String[]{Manifest.permission.CAMERA}, 1); @@ -56,17 +59,23 @@ public class HomeFragment extends FragmentBase 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + && grantResults[0] == PackageManager.PERMISSION_GRANTED) + { cameraAccessAction.invoke(true); - } else { + } + else + { cameraAccessAction.invoke(false); } } diff --git a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/loading/LoadingFragmentVM.java b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/loading/LoadingFragmentVM.java index ecce7a74e..39a93450e 100644 --- a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/loading/LoadingFragmentVM.java +++ b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/loading/LoadingFragmentVM.java @@ -31,7 +31,7 @@ public class LoadingFragmentVM extends ViewModelBase { super.onViewAttached(view); - new CountDownTimer(2000,50) + new CountDownTimer(5000,50) { @Override @@ -44,7 +44,7 @@ public class LoadingFragmentVM extends ViewModelBase public void onFinish() { loadingProgress.set(2000); - navigationProvider.navigateTo(NavigationView.Home, false); + navigationProvider.navigateTo(NavigationView.Capture, false); } }.start(); } diff --git a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/main/MainActivity.java b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/main/MainActivity.java index 74e3d5707..5ff99751f 100644 --- a/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/main/MainActivity.java +++ b/Software/Android_Studio/ColorCapture/app/src/main/java/com/twine/colorcapture/views/main/MainActivity.java @@ -8,6 +8,8 @@ import android.support.v4.widget.DrawerLayout; import android.util.Log; import android.view.Gravity; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; @@ -18,6 +20,7 @@ import android.widget.LinearLayout; import com.twine.colorcapture.App; import com.twine.colorcapture.R; +import com.twine.colorcapture.controls.ToggleImageButton; import com.twine.colorcapture.core.IAction; import com.twine.colorcapture.core.Task; import com.twine.colorcapture.databinding.ActivityMainBinding; @@ -42,7 +45,7 @@ public class MainActivity extends ActivityBase { private INavigationProvider navigationProvider; + + public DependencyProperty isMoreToggled; + public DependencyProperty isCaptureToggled; + public DependencyProperty isMyColorsToggled; @Inject public MainActivityVM(Bus eventBus, INotificationProvider notificationProvider, INavigationProvider navigationProvider) { this.navigationProvider = navigationProvider; + isMoreToggled = new DependencyProperty(false,this::onMoreToggled); + isCaptureToggled = new DependencyProperty(true,this::onCaptureToggled); + isMyColorsToggled = new DependencyProperty(false,this::onMyColorsToggled); + } + + private void onMyColorsToggled(DependencyProperty booleanDependencyProperty, Boolean value) + { + isMoreToggled.set(false); + isCaptureToggled.set(false); + } + + private void onCaptureToggled(DependencyProperty booleanDependencyProperty, Boolean value) + { + isMoreToggled.set(false); + isMyColorsToggled.set(false); + } + + private void onMoreToggled(DependencyProperty booleanDependencyProperty, Boolean value) + { + isCaptureToggled.set(false); + isMyColorsToggled.set(false); } } diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/logo.png new file mode 100644 index 000000000..d603442d6 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_normal.png new file mode 100644 index 000000000..8f8c4e153 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_selected.png new file mode 100644 index 000000000..28b678a74 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/more_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/my_colors.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/my_colors.png new file mode 100644 index 000000000..64433b097 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/my_colors.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/selected.png new file mode 100644 index 000000000..59b34c80f Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twine_logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twine_logo.png new file mode 100644 index 000000000..bad8273ba Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twine_logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_normal.png new file mode 100644 index 000000000..79705e9ca Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_selected.png new file mode 100644 index 000000000..3ca5bf9b7 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-hdpi/twinesnap_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/logo.png new file mode 100644 index 000000000..d9dc2cf15 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_normal.png new file mode 100644 index 000000000..6c8ce0b9a Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_selected.png new file mode 100644 index 000000000..179384dd0 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/more_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/my_colors.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/my_colors.png new file mode 100644 index 000000000..c38fbefc1 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/my_colors.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/selected.png new file mode 100644 index 000000000..0a5041bb9 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twine_logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twine_logo.png new file mode 100644 index 000000000..0c56ba31b Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twine_logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_normal.png new file mode 100644 index 000000000..3999dfb84 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_selected.png new file mode 100644 index 000000000..6bd9f3638 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-mdpi/twinesnap_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/logo.png new file mode 100644 index 000000000..f6b3bf9be Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_normal.png new file mode 100644 index 000000000..a540c41be Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_selected.png new file mode 100644 index 000000000..60494cce8 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/more_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/my_colors.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/my_colors.png new file mode 100644 index 000000000..2eae175fd Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/my_colors.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/selected.png new file mode 100644 index 000000000..e1aa19889 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twine_logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twine_logo.png new file mode 100644 index 000000000..9009dd34a Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twine_logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_normal.png new file mode 100644 index 000000000..bf6509ea2 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_selected.png new file mode 100644 index 000000000..f71ff6ea5 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xhdpi/twinesnap_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/logo.png new file mode 100644 index 000000000..fe384b6dd Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_normal.png new file mode 100644 index 000000000..edbed5b5e Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_selected.png new file mode 100644 index 000000000..263622f94 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/more_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/my_colors.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/my_colors.png new file mode 100644 index 000000000..1570ddb97 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/my_colors.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/selected.png new file mode 100644 index 000000000..6c4d944f2 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twine_logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twine_logo.png new file mode 100644 index 000000000..ce0f15c34 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twine_logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_normal.png new file mode 100644 index 000000000..6b098152f Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_selected.png new file mode 100644 index 000000000..94e490595 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxhdpi/twinesnap_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/logo.png new file mode 100644 index 000000000..b173a3c86 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_normal.png new file mode 100644 index 000000000..1f009161c Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_selected.png new file mode 100644 index 000000000..80b2611cf Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/more_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/my_colors.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/my_colors.png new file mode 100644 index 000000000..b816454ed Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/my_colors.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/selected.png new file mode 100644 index 000000000..705b88dfe Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twine_logo.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twine_logo.png new file mode 100644 index 000000000..d9ff791bc Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twine_logo.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_normal.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_normal.png new file mode 100644 index 000000000..f48411cb9 Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_normal.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_selected.png b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_selected.png new file mode 100644 index 000000000..3741bdb4a Binary files /dev/null and b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable-xxxhdpi/twinesnap_selected.png differ diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/background_gradient.xml b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/background_gradient.xml new file mode 100644 index 000000000..9a95bd708 --- /dev/null +++ b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/background_gradient.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border.xml b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border.xml new file mode 100644 index 000000000..2f0177da9 --- /dev/null +++ b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border_shadow.xml b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border_shadow.xml new file mode 100644 index 000000000..cf3d424af --- /dev/null +++ b/Software/Android_Studio/ColorCapture/app/src/main/res/drawable/border_shadow.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Android_Studio/ColorCapture/app/src/main/res/layout/activity_main.xml b/Software/Android_Studio/ColorCapture/app/src/main/res/layout/activity_main.xml index 1d7d77ad4..e650b8328 100644 --- a/Software/Android_Studio/ColorCapture/app/src/main/res/layout/activity_main.xml +++ b/Software/Android_Studio/ColorCapture/app/src/main/res/layout/activity_main.xml @@ -21,60 +21,64 @@ + android:visibility="visible"> - - - + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs new file mode 100644 index 000000000..5ed165aad --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs @@ -0,0 +1,58 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Input; + +using Tango.Scripting.Editors.Editing; + +namespace Tango.Scripting.Editors.CodeCompletion +{ + /// + /// Insight window that shows an OverloadViewer. + /// + public class OverloadInsightWindow : InsightWindow + { + OverloadViewer overloadViewer = new OverloadViewer(); + + /// + /// Creates a new OverloadInsightWindow. + /// + public OverloadInsightWindow(TextArea textArea) : base(textArea) + { + overloadViewer.Margin = new Thickness(2,0,0,0); + this.Content = overloadViewer; + } + + /// + /// Gets/Sets the item provider. + /// + public IOverloadProvider Provider { + get { return overloadViewer.Provider; } + set { overloadViewer.Provider = value; } + } + + /// + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + if (!e.Handled && this.Provider.Count > 1) { + switch (e.Key) { + case Key.Up: + e.Handled = true; + overloadViewer.ChangeIndex(-1); + break; + case Key.Down: + e.Handled = true; + overloadViewer.ChangeIndex(+1); + break; + } + if (e.Handled) { + UpdateLayout(); + UpdatePosition(); + } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs new file mode 100644 index 000000000..f70229a8d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs @@ -0,0 +1,101 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; + +namespace Tango.Scripting.Editors.CodeCompletion +{ + /// + /// Represents a text between "Up" and "Down" buttons. + /// + public class OverloadViewer : Control + { + static OverloadViewer() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OverloadViewer), + new FrameworkPropertyMetadata(typeof(OverloadViewer))); + } + + /// + /// The text property. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register("Text", typeof(string), typeof(OverloadViewer)); + + /// + /// Gets/Sets the text between the Up and Down buttons. + /// + public string Text { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + Button upButton = (Button)this.Template.FindName("PART_UP", this); + upButton.Click += (sender, e) => { + e.Handled = true; + ChangeIndex(-1); + }; + + Button downButton = (Button)this.Template.FindName("PART_DOWN", this); + downButton.Click += (sender, e) => { + e.Handled = true; + ChangeIndex(+1); + }; + } + + /// + /// The ItemProvider property. + /// + public static readonly DependencyProperty ProviderProperty = + DependencyProperty.Register("Provider", typeof(IOverloadProvider), typeof(OverloadViewer)); + + /// + /// Gets/Sets the item provider. + /// + public IOverloadProvider Provider { + get { return (IOverloadProvider)GetValue(ProviderProperty); } + set { SetValue(ProviderProperty, value); } + } + + /// + /// Changes the selected index. + /// + /// The relative index change - usual values are +1 or -1. + public void ChangeIndex(int relativeIndexChange) + { + IOverloadProvider p = this.Provider; + if (p != null) { + int newIndex = p.SelectedIndex + relativeIndexChange; + if (newIndex < 0) + newIndex = p.Count - 1; + if (newIndex >= p.Count) + newIndex = 0; + p.SelectedIndex = newIndex; + } + } + } + + sealed class CollapseIfSingleOverloadConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return ((int)value < 2) ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityConverter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityConverter.cs new file mode 100644 index 000000000..7412e4a67 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace Tango.Scripting.Editors.Converters +{ + public class BooleanToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityInversedConverter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityInversedConverter.cs new file mode 100644 index 000000000..c91aa45fd --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Converters/BooleanToVisibilityInversedConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace Tango.Scripting.Editors.Converters +{ + public class BooleanToVisibilityInversedConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ChangeTrackingCheckpoint.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ChangeTrackingCheckpoint.cs new file mode 100644 index 000000000..283731da8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ChangeTrackingCheckpoint.cs @@ -0,0 +1,140 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// A checkpoint that allows tracking changes to a TextDocument. + /// + /// Use to create a checkpoint. + /// + /// + /// + /// The class allows tracking document changes, even from background threads. + /// Once you have two checkpoints, you can call to retrieve the complete list + /// of document changes that happened between those versions of the document. + /// + public sealed class ChangeTrackingCheckpoint + { + // Object that is unique per document. + // Used to determine if two checkpoints belong to the same document. + // We don't use a reference to the document itself to allow the GC to reclaim the document memory + // even if there are still references to checkpoints. + readonly object documentIdentifier; + + // 'value' is the change from the previous checkpoint to this checkpoint + // TODO: store the change in the older checkpoint instead - if only a reference to the + // newest document version exists, the GC should be able to collect all DocumentChangeEventArgs. + readonly DocumentChangeEventArgs value; + readonly int id; + ChangeTrackingCheckpoint next; + + internal ChangeTrackingCheckpoint(object documentIdentifier) + { + this.documentIdentifier = documentIdentifier; + } + + internal ChangeTrackingCheckpoint(object documentIdentifier, DocumentChangeEventArgs value, int id) + { + this.documentIdentifier = documentIdentifier; + this.value = value; + this.id = id; + } + + internal ChangeTrackingCheckpoint Append(DocumentChangeEventArgs change) + { + Debug.Assert(this.next == null); + this.next = new ChangeTrackingCheckpoint(this.documentIdentifier, change, unchecked( this.id + 1 )); + return this.next; + } + + /// + /// Creates a change tracking checkpoint for the specified document. + /// This method is thread-safe. + /// If you need a ChangeTrackingCheckpoint that's consistent with a snapshot of the document, + /// use . + /// + public static ChangeTrackingCheckpoint Create(TextDocument document) + { + if (document == null) + throw new ArgumentNullException("document"); + return document.CreateChangeTrackingCheckpoint(); + } + + /// + /// Gets whether this checkpoint belongs to the same document as the other checkpoint. + /// + public bool BelongsToSameDocumentAs(ChangeTrackingCheckpoint other) + { + if (other == null) + throw new ArgumentNullException("other"); + return documentIdentifier == other.documentIdentifier; + } + + /// + /// Compares the age of this checkpoint to the other checkpoint. + /// + /// This method is thread-safe. + /// Raised if 'other' belongs to a different document than this checkpoint. + /// -1 if this checkpoint is older than . + /// 0 if this==. + /// 1 if this checkpoint is newer than . + public int CompareAge(ChangeTrackingCheckpoint other) + { + if (other == null) + throw new ArgumentNullException("other"); + if (other.documentIdentifier != this.documentIdentifier) + throw new ArgumentException("Checkpoints do not belong to the same document."); + // We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1. + // This is guaranteed on x86 because so many checkpoints don't fit into memory. + return Math.Sign(unchecked( this.id - other.id )); + } + + /// + /// Gets the changes from this checkpoint to the other checkpoint. + /// If 'other' is older than this checkpoint, reverse changes are calculated. + /// + /// This method is thread-safe. + /// Raised if 'other' belongs to a different document than this checkpoint. + public IEnumerable GetChangesTo(ChangeTrackingCheckpoint other) + { + int result = CompareAge(other); + if (result < 0) + return GetForwardChanges(other); + else if (result > 0) + return other.GetForwardChanges(this).Reverse().Select(change => change.Invert()); + else + return Empty.Array; + } + + IEnumerable GetForwardChanges(ChangeTrackingCheckpoint other) + { + // Return changes from this(exclusive) to other(inclusive). + ChangeTrackingCheckpoint node = this; + do { + node = node.next; + yield return node.value; + } while (node != other); + } + + /// + /// Calculates where the offset has moved in the other buffer version. + /// + /// This method is thread-safe. + /// Raised if 'other' belongs to a different document than this checkpoint. + public int MoveOffsetTo(ChangeTrackingCheckpoint other, int oldOffset, AnchorMovementType movement) + { + int offset = oldOffset; + foreach (DocumentChangeEventArgs e in GetChangesTo(other)) { + offset = e.GetNewOffset(offset, movement); + } + return offset; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeEventArgs.cs new file mode 100644 index 000000000..17307680a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeEventArgs.cs @@ -0,0 +1,131 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Describes a change of the document text. + /// This class is thread-safe. + /// + [Serializable] + public class DocumentChangeEventArgs : EventArgs + { + /// + /// The offset at which the change occurs. + /// + public int Offset { get; private set; } + + /// + /// The text that was removed. + /// + public string RemovedText { get; private set; } + + /// + /// The number of characters removed. + /// + public int RemovalLength { + get { return RemovedText.Length; } + } + + /// + /// The text that was inserted. + /// + public string InsertedText { get; private set; } + + /// + /// The number of characters inserted. + /// + public int InsertionLength { + get { return InsertedText.Length; } + } + + volatile OffsetChangeMap offsetChangeMap; + + /// + /// Gets the OffsetChangeMap associated with this document change. + /// + /// The OffsetChangeMap instance is guaranteed to be frozen and thus thread-safe. + public OffsetChangeMap OffsetChangeMap { + get { + OffsetChangeMap map = offsetChangeMap; + if (map == null) { + // create OffsetChangeMap on demand + map = OffsetChangeMap.FromSingleElement(CreateSingleChangeMapEntry()); + offsetChangeMap = map; + } + return map; + } + } + + internal OffsetChangeMapEntry CreateSingleChangeMapEntry() + { + return new OffsetChangeMapEntry(this.Offset, this.RemovalLength, this.InsertionLength); + } + + /// + /// Gets the OffsetChangeMap, or null if the default offset map (=single replacement) is being used. + /// + internal OffsetChangeMap OffsetChangeMapOrNull { + get { + return offsetChangeMap; + } + } + + /// + /// Gets the new offset where the specified offset moves after this document change. + /// + public int GetNewOffset(int offset, AnchorMovementType movementType) + { + if (offsetChangeMap != null) + return offsetChangeMap.GetNewOffset(offset, movementType); + else + return CreateSingleChangeMapEntry().GetNewOffset(offset, movementType); + } + + /// + /// Creates a new DocumentChangeEventArgs object. + /// + public DocumentChangeEventArgs(int offset, string removedText, string insertedText) + : this(offset, removedText, insertedText, null) + { + } + + /// + /// Creates a new DocumentChangeEventArgs object. + /// + public DocumentChangeEventArgs(int offset, string removedText, string insertedText, OffsetChangeMap offsetChangeMap) + { + ThrowUtil.CheckNotNegative(offset, "offset"); + ThrowUtil.CheckNotNull(removedText, "removedText"); + ThrowUtil.CheckNotNull(insertedText, "insertedText"); + + this.Offset = offset; + this.RemovedText = removedText; + this.InsertedText = insertedText; + + if (offsetChangeMap != null) { + if (!offsetChangeMap.IsFrozen) + throw new ArgumentException("The OffsetChangeMap must be frozen before it can be used in DocumentChangeEventArgs"); + if (!offsetChangeMap.IsValidForDocumentChange(offset, removedText.Length, insertedText.Length)) + throw new ArgumentException("OffsetChangeMap is not valid for this document change", "offsetChangeMap"); + this.offsetChangeMap = offsetChangeMap; + } + } + + /// + /// Creates DocumentChangeEventArgs for the reverse change. + /// + public DocumentChangeEventArgs Invert() + { + OffsetChangeMap map = this.OffsetChangeMapOrNull; + if (map != null) { + map = map.Invert(); + map.Freeze(); + } + return new DocumentChangeEventArgs(this.Offset, this.InsertedText, this.RemovedText, map); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeOperation.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeOperation.cs new file mode 100644 index 000000000..f24b7ed4d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentChangeOperation.cs @@ -0,0 +1,52 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Describes a change to a TextDocument. + /// + sealed class DocumentChangeOperation : IUndoableOperationWithContext + { + TextDocument document; + DocumentChangeEventArgs change; + + public DocumentChangeOperation(TextDocument document, DocumentChangeEventArgs change) + { + this.document = document; + this.change = change; + } + + public void Undo(UndoStack stack) + { + Debug.Assert(stack.state == UndoStack.StatePlayback); + stack.RegisterAffectedDocument(document); + stack.state = UndoStack.StatePlaybackModifyDocument; + this.Undo(); + stack.state = UndoStack.StatePlayback; + } + + public void Redo(UndoStack stack) + { + Debug.Assert(stack.state == UndoStack.StatePlayback); + stack.RegisterAffectedDocument(document); + stack.state = UndoStack.StatePlaybackModifyDocument; + this.Redo(); + stack.state = UndoStack.StatePlayback; + } + + public void Undo() + { + OffsetChangeMap map = change.OffsetChangeMapOrNull; + document.Replace(change.Offset, change.InsertionLength, change.RemovedText, map != null ? map.Invert() : null); + } + + public void Redo() + { + document.Replace(change.Offset, change.RemovalLength, change.InsertedText, change.OffsetChangeMapOrNull); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLine.cs new file mode 100644 index 000000000..746f31637 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLine.cs @@ -0,0 +1,242 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Represents a line inside a . + /// + /// + /// + /// The collection contains one DocumentLine instance + /// for every line in the document. This collection is read-only to user code and is automatically + /// updated to reflect the current document content. + /// + /// + /// Internally, the DocumentLine instances are arranged in a binary tree that allows for both efficient updates and lookup. + /// Converting between offset and line number is possible in O(lg N) time, + /// and the data structure also updates all offsets in O(lg N) whenever a line is inserted or removed. + /// + /// + public sealed partial class DocumentLine : ISegment + { + #region Constructor + #if DEBUG + // Required for thread safety check which is done only in debug builds. + // To save space, we don't store the document reference in release builds as we don't need it there. + readonly TextDocument document; + #endif + + internal bool isDeleted; + + internal DocumentLine(TextDocument document) + { + #if DEBUG + Debug.Assert(document != null); + this.document = document; + #endif + } + + [Conditional("DEBUG")] + void DebugVerifyAccess() + { + #if DEBUG + document.DebugVerifyAccess(); + #endif + } + #endregion + + #region Events +// /// +// /// Is raised when the line is deleted. +// /// +// public event EventHandler Deleted; +// +// /// +// /// Is raised when the line's text changes. +// /// +// public event EventHandler TextChanged; +// +// /// +// /// Raises the Deleted or TextChanged event. +// /// +// internal void RaiseChanged() +// { +// if (IsDeleted) { +// if (Deleted != null) +// Deleted(this, EventArgs.Empty); +// } else { +// if (TextChanged != null) +// TextChanged(this, EventArgs.Empty); +// } +// } + #endregion + + #region Properties stored in tree + /// + /// Gets if this line was deleted from the document. + /// + public bool IsDeleted { + get { + DebugVerifyAccess(); + return isDeleted; + } + } + + /// + /// Gets the number of this line. + /// Runtime: O(log n) + /// + /// The line was deleted. + public int LineNumber { + get { + if (IsDeleted) + throw new InvalidOperationException(); + return DocumentLineTree.GetIndexFromNode(this) + 1; + } + } + + /// + /// Gets the starting offset of the line in the document's text. + /// Runtime: O(log n) + /// + /// The line was deleted. + public int Offset { + get { + if (IsDeleted) + throw new InvalidOperationException(); + return DocumentLineTree.GetOffsetFromNode(this); + } + } + + /// + /// Gets the end offset of the line in the document's text (the offset before the line delimiter). + /// Runtime: O(log n) + /// + /// The line was deleted. + /// EndOffset = + . + public int EndOffset { + get { return this.Offset + this.Length; } + } + #endregion + + #region Length + int totalLength; + byte delimiterLength; + + /// + /// Gets the length of this line. The length does not include the line delimiter. O(1) + /// + /// This property is still available even if the line was deleted; + /// in that case, it contains the line's length before the deletion. + public int Length { + get { + DebugVerifyAccess(); + return totalLength - delimiterLength; + } + } + + /// + /// Gets the length of this line, including the line delimiter. O(1) + /// + /// This property is still available even if the line was deleted; + /// in that case, it contains the line's length before the deletion. + public int TotalLength { + get { + DebugVerifyAccess(); + return totalLength; + } + internal set { + // this is set by DocumentLineTree + totalLength = value; + } + } + + /// + /// Gets the length of the line delimiter. + /// The value is 1 for single "\r" or "\n", 2 for the "\r\n" sequence; + /// and 0 for the last line in the document. + /// + /// This property is still available even if the line was deleted; + /// in that case, it contains the line delimiter's length before the deletion. + public int DelimiterLength { + get { + DebugVerifyAccess(); + return delimiterLength; + } + internal set { + Debug.Assert(value >= 0 && value <= 2); + delimiterLength = (byte)value; + } + } + #endregion + + #region Previous / Next Line + /// + /// Gets the next line in the document. + /// + /// The line following this line, or null if this is the last line. + public DocumentLine NextLine { + get { + DebugVerifyAccess(); + + if (right != null) { + return right.LeftMost; + } else { + DocumentLine node = this; + DocumentLine oldNode; + do { + oldNode = node; + node = node.parent; + // we are on the way up from the right part, don't output node again + } while (node != null && node.right == oldNode); + return node; + } + } + } + + /// + /// Gets the previous line in the document. + /// + /// The line before this line, or null if this is the first line. + public DocumentLine PreviousLine { + get { + DebugVerifyAccess(); + + if (left != null) { + return left.RightMost; + } else { + DocumentLine node = this; + DocumentLine oldNode; + do { + oldNode = node; + node = node.parent; + // we are on the way up from the left part, don't output node again + } while (node != null && node.left == oldNode); + return node; + } + } + } + #endregion + + #region ToString + /// + /// Gets a string with debug output showing the line number and offset. + /// Does not include the line's text. + /// + public override string ToString() + { + if (IsDeleted) + return "[DocumentLine deleted]"; + else + return string.Format( + CultureInfo.InvariantCulture, + "[DocumentLine Number={0} Offset={1} Length={2}]", LineNumber, Offset, Length); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLineTree.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLineTree.cs new file mode 100644 index 000000000..927dc3e01 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/DocumentLineTree.cs @@ -0,0 +1,712 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Tango.Scripting.Editors.Document +{ + using LineNode = DocumentLine; + + /// + /// Data structure for efficient management of the document lines (most operations are O(lg n)). + /// This implements an augmented red-black tree. + /// See for the augmented data. + /// + /// NOTE: The tree is never empty, initially it contains an empty line. + /// + sealed class DocumentLineTree : IList + { + #region Constructor + readonly TextDocument document; + LineNode root; + + public DocumentLineTree(TextDocument document) + { + this.document = document; + + DocumentLine emptyLine = new DocumentLine(document); + root = emptyLine.InitLineNode(); + } + #endregion + + #region Rotation callbacks + internal static void UpdateAfterChildrenChange(LineNode node) + { + int totalCount = 1; + int totalLength = node.TotalLength; + if (node.left != null) { + totalCount += node.left.nodeTotalCount; + totalLength += node.left.nodeTotalLength; + } + if (node.right != null) { + totalCount += node.right.nodeTotalCount; + totalLength += node.right.nodeTotalLength; + } + if (totalCount != node.nodeTotalCount + || totalLength != node.nodeTotalLength) + { + node.nodeTotalCount = totalCount; + node.nodeTotalLength = totalLength; + if (node.parent != null) UpdateAfterChildrenChange(node.parent); + } + } + + static void UpdateAfterRotateLeft(LineNode node) + { + UpdateAfterChildrenChange(node); + + // not required: rotations only happen on insertions/deletions + // -> totalCount changes -> the parent is always updated + //UpdateAfterChildrenChange(node.parent); + } + + static void UpdateAfterRotateRight(LineNode node) + { + UpdateAfterChildrenChange(node); + + // not required: rotations only happen on insertions/deletions + // -> totalCount changes -> the parent is always updated + //UpdateAfterChildrenChange(node.parent); + } + #endregion + + #region RebuildDocument + /// + /// Rebuild the tree, in O(n). + /// + public void RebuildTree(List documentLines) + { + LineNode[] nodes = new LineNode[documentLines.Count]; + for (int i = 0; i < documentLines.Count; i++) { + DocumentLine ls = documentLines[i]; + LineNode node = ls.InitLineNode(); + nodes[i] = node; + } + Debug.Assert(nodes.Length > 0); + // now build the corresponding balanced tree + int height = GetTreeHeight(nodes.Length); + Debug.WriteLine("DocumentLineTree will have height: " + height); + root = BuildTree(nodes, 0, nodes.Length, height); + root.color = BLACK; + #if DEBUG + CheckProperties(); + #endif + } + + internal static int GetTreeHeight(int size) + { + if (size == 0) + return 0; + else + return GetTreeHeight(size / 2) + 1; + } + + /// + /// build a tree from a list of nodes + /// + LineNode BuildTree(LineNode[] nodes, int start, int end, int subtreeHeight) + { + Debug.Assert(start <= end); + if (start == end) { + return null; + } + int middle = (start + end) / 2; + LineNode node = nodes[middle]; + node.left = BuildTree(nodes, start, middle, subtreeHeight - 1); + node.right = BuildTree(nodes, middle + 1, end, subtreeHeight - 1); + if (node.left != null) node.left.parent = node; + if (node.right != null) node.right.parent = node; + if (subtreeHeight == 1) + node.color = RED; + UpdateAfterChildrenChange(node); + return node; + } + #endregion + + #region GetNodeBy... / Get...FromNode + LineNode GetNodeByIndex(int index) + { + Debug.Assert(index >= 0); + Debug.Assert(index < root.nodeTotalCount); + LineNode node = root; + while (true) { + if (node.left != null && index < node.left.nodeTotalCount) { + node = node.left; + } else { + if (node.left != null) { + index -= node.left.nodeTotalCount; + } + if (index == 0) + return node; + index--; + node = node.right; + } + } + } + + internal static int GetIndexFromNode(LineNode node) + { + int index = (node.left != null) ? node.left.nodeTotalCount : 0; + while (node.parent != null) { + if (node == node.parent.right) { + if (node.parent.left != null) + index += node.parent.left.nodeTotalCount; + index++; + } + node = node.parent; + } + return index; + } + + LineNode GetNodeByOffset(int offset) + { + Debug.Assert(offset >= 0); + Debug.Assert(offset <= root.nodeTotalLength); + if (offset == root.nodeTotalLength) { + return root.RightMost; + } + LineNode node = root; + while (true) { + if (node.left != null && offset < node.left.nodeTotalLength) { + node = node.left; + } else { + if (node.left != null) { + offset -= node.left.nodeTotalLength; + } + offset -= node.TotalLength; + if (offset < 0) + return node; + node = node.right; + } + } + } + + internal static int GetOffsetFromNode(LineNode node) + { + int offset = (node.left != null) ? node.left.nodeTotalLength : 0; + while (node.parent != null) { + if (node == node.parent.right) { + if (node.parent.left != null) + offset += node.parent.left.nodeTotalLength; + offset += node.parent.TotalLength; + } + node = node.parent; + } + return offset; + } + #endregion + + #region GetLineBy + public DocumentLine GetByNumber(int number) + { + return GetNodeByIndex(number - 1); + } + + public DocumentLine GetByOffset(int offset) + { + return GetNodeByOffset(offset); + } + #endregion + + #region LineCount + public int LineCount { + get { + return root.nodeTotalCount; + } + } + #endregion + + #region CheckProperties + #if DEBUG + [Conditional("DATACONSISTENCYTEST")] + internal void CheckProperties() + { + Debug.Assert(root.nodeTotalLength == document.TextLength); + CheckProperties(root); + + // check red-black property: + int blackCount = -1; + CheckNodeProperties(root, null, RED, 0, ref blackCount); + } + + void CheckProperties(LineNode node) + { + int totalCount = 1; + int totalLength = node.TotalLength; + if (node.left != null) { + CheckProperties(node.left); + totalCount += node.left.nodeTotalCount; + totalLength += node.left.nodeTotalLength; + } + if (node.right != null) { + CheckProperties(node.right); + totalCount += node.right.nodeTotalCount; + totalLength += node.right.nodeTotalLength; + } + Debug.Assert(node.nodeTotalCount == totalCount); + Debug.Assert(node.nodeTotalLength == totalLength); + } + + /* + 1. A node is either red or black. + 2. The root is black. + 3. All leaves are black. (The leaves are the NIL children.) + 4. Both children of every red node are black. (So every red node must have a black parent.) + 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) + */ + void CheckNodeProperties(LineNode node, LineNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) + { + if (node == null) return; + + Debug.Assert(node.parent == parentNode); + + if (parentColor == RED) { + Debug.Assert(node.color == BLACK); + } + if (node.color == BLACK) { + blackCount++; + } + if (node.left == null && node.right == null) { + // node is a leaf node: + if (expectedBlackCount == -1) + expectedBlackCount = blackCount; + else + Debug.Assert(expectedBlackCount == blackCount); + } + CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); + CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public string GetTreeAsString() + { + StringBuilder b = new StringBuilder(); + AppendTreeToString(root, b, 0); + return b.ToString(); + } + + static void AppendTreeToString(LineNode node, StringBuilder b, int indent) + { + if (node.color == RED) + b.Append("RED "); + else + b.Append("BLACK "); + b.AppendLine(node.ToString()); + indent += 2; + if (node.left != null) { + b.Append(' ', indent); + b.Append("L: "); + AppendTreeToString(node.left, b, indent); + } + if (node.right != null) { + b.Append(' ', indent); + b.Append("R: "); + AppendTreeToString(node.right, b, indent); + } + } + #endif + #endregion + + #region Insert/Remove lines + public void RemoveLine(DocumentLine line) + { + RemoveNode(line); + line.isDeleted = true; + } + + public DocumentLine InsertLineAfter(DocumentLine line, int totalLength) + { + DocumentLine newLine = new DocumentLine(document); + newLine.TotalLength = totalLength; + + InsertAfter(line, newLine); + return newLine; + } + + void InsertAfter(LineNode node, DocumentLine newLine) + { + LineNode newNode = newLine.InitLineNode(); + if (node.right == null) { + InsertAsRight(node, newNode); + } else { + InsertAsLeft(node.right.LeftMost, newNode); + } + } + #endregion + + #region Red/Black Tree + internal const bool RED = true; + internal const bool BLACK = false; + + void InsertAsLeft(LineNode parentNode, LineNode newNode) + { + Debug.Assert(parentNode.left == null); + parentNode.left = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAfterChildrenChange(parentNode); + FixTreeOnInsert(newNode); + } + + void InsertAsRight(LineNode parentNode, LineNode newNode) + { + Debug.Assert(parentNode.right == null); + parentNode.right = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAfterChildrenChange(parentNode); + FixTreeOnInsert(newNode); + } + + void FixTreeOnInsert(LineNode node) + { + Debug.Assert(node != null); + Debug.Assert(node.color == RED); + Debug.Assert(node.left == null || node.left.color == BLACK); + Debug.Assert(node.right == null || node.right.color == BLACK); + + LineNode parentNode = node.parent; + if (parentNode == null) { + // we inserted in the root -> the node must be black + // since this is a root node, making the node black increments the number of black nodes + // on all paths by one, so it is still the same for all paths. + node.color = BLACK; + return; + } + if (parentNode.color == BLACK) { + // if the parent node where we inserted was black, our red node is placed correctly. + // since we inserted a red node, the number of black nodes on each path is unchanged + // -> the tree is still balanced + return; + } + // parentNode is red, so there is a conflict here! + + // because the root is black, parentNode is not the root -> there is a grandparent node + LineNode grandparentNode = parentNode.parent; + LineNode uncleNode = Sibling(parentNode); + if (uncleNode != null && uncleNode.color == RED) { + parentNode.color = BLACK; + uncleNode.color = BLACK; + grandparentNode.color = RED; + FixTreeOnInsert(grandparentNode); + return; + } + // now we know: parent is red but uncle is black + // First rotation: + if (node == parentNode.right && parentNode == grandparentNode.left) { + RotateLeft(parentNode); + node = node.left; + } else if (node == parentNode.left && parentNode == grandparentNode.right) { + RotateRight(parentNode); + node = node.right; + } + // because node might have changed, reassign variables: + parentNode = node.parent; + grandparentNode = parentNode.parent; + + // Now recolor a bit: + parentNode.color = BLACK; + grandparentNode.color = RED; + // Second rotation: + if (node == parentNode.left && parentNode == grandparentNode.left) { + RotateRight(grandparentNode); + } else { + // because of the first rotation, this is guaranteed: + Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); + RotateLeft(grandparentNode); + } + } + + void RemoveNode(LineNode removedNode) + { + if (removedNode.left != null && removedNode.right != null) { + // replace removedNode with it's in-order successor + + LineNode leftMost = removedNode.right.LeftMost; + RemoveNode(leftMost); // remove leftMost from its current location + + // and overwrite the removedNode with it + ReplaceNode(removedNode, leftMost); + leftMost.left = removedNode.left; + if (leftMost.left != null) leftMost.left.parent = leftMost; + leftMost.right = removedNode.right; + if (leftMost.right != null) leftMost.right.parent = leftMost; + leftMost.color = removedNode.color; + + UpdateAfterChildrenChange(leftMost); + if (leftMost.parent != null) UpdateAfterChildrenChange(leftMost.parent); + return; + } + + // now either removedNode.left or removedNode.right is null + // get the remaining child + LineNode parentNode = removedNode.parent; + LineNode childNode = removedNode.left ?? removedNode.right; + ReplaceNode(removedNode, childNode); + if (parentNode != null) UpdateAfterChildrenChange(parentNode); + if (removedNode.color == BLACK) { + if (childNode != null && childNode.color == RED) { + childNode.color = BLACK; + } else { + FixTreeOnDelete(childNode, parentNode); + } + } + } + + void FixTreeOnDelete(LineNode node, LineNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (parentNode == null) + return; + + // warning: node may be null + LineNode sibling = Sibling(node, parentNode); + if (sibling.color == RED) { + parentNode.color = RED; + sibling.color = BLACK; + if (node == parentNode.left) { + RotateLeft(parentNode); + } else { + RotateRight(parentNode); + } + + sibling = Sibling(node, parentNode); // update value of sibling after rotation + } + + if (parentNode.color == BLACK + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + FixTreeOnDelete(parentNode, parentNode.parent); + return; + } + + if (parentNode.color == RED + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + parentNode.color = BLACK; + return; + } + + if (node == parentNode.left && + sibling.color == BLACK && + GetColor(sibling.left) == RED && + GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + sibling.left.color = BLACK; + RotateRight(sibling); + } + else if (node == parentNode.right && + sibling.color == BLACK && + GetColor(sibling.right) == RED && + GetColor(sibling.left) == BLACK) + { + sibling.color = RED; + sibling.right.color = BLACK; + RotateLeft(sibling); + } + sibling = Sibling(node, parentNode); // update value of sibling after rotation + + sibling.color = parentNode.color; + parentNode.color = BLACK; + if (node == parentNode.left) { + if (sibling.right != null) { + Debug.Assert(sibling.right.color == RED); + sibling.right.color = BLACK; + } + RotateLeft(parentNode); + } else { + if (sibling.left != null) { + Debug.Assert(sibling.left.color == RED); + sibling.left.color = BLACK; + } + RotateRight(parentNode); + } + } + + void ReplaceNode(LineNode replacedNode, LineNode newNode) + { + if (replacedNode.parent == null) { + Debug.Assert(replacedNode == root); + root = newNode; + } else { + if (replacedNode.parent.left == replacedNode) + replacedNode.parent.left = newNode; + else + replacedNode.parent.right = newNode; + } + if (newNode != null) { + newNode.parent = replacedNode.parent; + } + replacedNode.parent = null; + } + + void RotateLeft(LineNode p) + { + // let q be p's right child + LineNode q = p.right; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's right child to be q's left child + p.right = q.left; + if (p.right != null) p.right.parent = p; + // set q's left child to be p + q.left = p; + p.parent = q; + UpdateAfterRotateLeft(p); + } + + void RotateRight(LineNode p) + { + // let q be p's left child + LineNode q = p.left; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's left child to be q's right child + p.left = q.right; + if (p.left != null) p.left.parent = p; + // set q's right child to be p + q.right = p; + p.parent = q; + UpdateAfterRotateRight(p); + } + + static LineNode Sibling(LineNode node) + { + if (node == node.parent.left) + return node.parent.right; + else + return node.parent.left; + } + + static LineNode Sibling(LineNode node, LineNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (node == parentNode.left) + return parentNode.right; + else + return parentNode.left; + } + + static bool GetColor(LineNode node) + { + return node != null ? node.color : BLACK; + } + #endregion + + #region IList implementation + DocumentLine IList.this[int index] { + get { + document.VerifyAccess(); + return GetByNumber(1 + index); + } + set { + throw new NotSupportedException(); + } + } + + int ICollection.Count { + get { + document.VerifyAccess(); + return LineCount; + } + } + + bool ICollection.IsReadOnly { + get { return true; } + } + + int IList.IndexOf(DocumentLine item) + { + document.VerifyAccess(); + if (item == null || item.IsDeleted) + return -1; + int index = item.LineNumber - 1; + if (index < LineCount && GetNodeByIndex(index) == item) + return index; + else + return -1; + } + + void IList.Insert(int index, DocumentLine item) + { + throw new NotSupportedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + void ICollection.Add(DocumentLine item) + { + throw new NotSupportedException(); + } + + void ICollection.Clear() + { + throw new NotSupportedException(); + } + + bool ICollection.Contains(DocumentLine item) + { + IList self = this; + return self.IndexOf(item) >= 0; + } + + void ICollection.CopyTo(DocumentLine[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (array.Length < LineCount) + throw new ArgumentException("The array is too small", "array"); + if (arrayIndex < 0 || arrayIndex + LineCount > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - LineCount)); + foreach (DocumentLine ls in this) { + array[arrayIndex++] = ls; + } + } + + bool ICollection.Remove(DocumentLine item) + { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() + { + document.VerifyAccess(); + return Enumerate(); + } + + IEnumerator Enumerate() + { + document.VerifyAccess(); + DocumentLine line = root.LeftMost; + while (line != null) { + yield return line; + line = line.NextLine; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/GapTextBuffer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/GapTextBuffer.cs new file mode 100644 index 000000000..a617284a9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/GapTextBuffer.cs @@ -0,0 +1,192 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Text; + +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /* + /// + /// Implementation of a gap text buffer. + /// + sealed class GapTextBuffer + { + char[] buffer = Empty.Array; + + /// + /// The current text content. + /// Is set to null whenever the buffer changes, and gets a value only when the + /// full text content is requested. + /// + string textContent; + + /// + /// last GetText result + /// + string lastGetTextResult; + int lastGetTextRequestOffset; + + int gapBeginOffset; + int gapEndOffset; + int gapLength; // gapLength == gapEndOffset - gapBeginOffset + + /// + /// when gap is too small for inserted text or gap is too large (exceeds maxGapLength), + /// a new buffer is reallocated with a new gap of at least this size. + /// + const int minGapLength = 128; + + /// + /// when the gap exceeds this size, reallocate a smaller buffer + /// + const int maxGapLength = 4096; + + public int Length { + get { + return buffer.Length - gapLength; + } + } + + /// + /// Gets the buffer content. + /// + public string Text { + get { + if (textContent == null) + textContent = GetText(0, Length); + return textContent; + } + set { + Debug.Assert(value != null); + textContent = value; lastGetTextResult = null; + buffer = new char[value.Length + minGapLength]; + value.CopyTo(0, buffer, 0, value.Length); + gapBeginOffset = value.Length; + gapEndOffset = buffer.Length; + gapLength = gapEndOffset - gapBeginOffset; + } + } + + public char GetCharAt(int offset) + { + return offset < gapBeginOffset ? buffer[offset] : buffer[offset + gapLength]; + } + + public string GetText(int offset, int length) + { + if (length == 0) + return string.Empty; + if (lastGetTextRequestOffset == offset && lastGetTextResult != null && length == lastGetTextResult.Length) + return lastGetTextResult; + + int end = offset + length; + string result; + if (end < gapBeginOffset) { + result = new string(buffer, offset, length); + } else if (offset > gapBeginOffset) { + result = new string(buffer, offset + gapLength, length); + } else { + int block1Size = gapBeginOffset - offset; + int block2Size = end - gapBeginOffset; + + StringBuilder buf = new StringBuilder(block1Size + block2Size); + buf.Append(buffer, offset, block1Size); + buf.Append(buffer, gapEndOffset, block2Size); + result = buf.ToString(); + } + lastGetTextRequestOffset = offset; + lastGetTextResult = result; + return result; + } + + /// + /// Inserts text at the specified offset. + /// + public void Insert(int offset, string text) + { + Debug.Assert(offset >= 0 && offset <= Length); + + if (text.Length == 0) + return; + + textContent = null; lastGetTextResult = null; + PlaceGap(offset, text.Length); + text.CopyTo(0, buffer, gapBeginOffset, text.Length); + gapBeginOffset += text.Length; + gapLength = gapEndOffset - gapBeginOffset; + } + + /// + /// Remove characters at . + /// Leave a gap of at least . + /// + public void Remove(int offset, int length, int reserveGapSize) + { + Debug.Assert(offset >= 0 && offset <= Length); + Debug.Assert(length >= 0 && offset + length <= Length); + Debug.Assert(reserveGapSize >= 0); + + if (length == 0) + return; + + textContent = null; lastGetTextResult = null; + PlaceGap(offset, reserveGapSize - length); + gapEndOffset += length; // delete removed text + gapLength = gapEndOffset - gapBeginOffset; + if (gapLength - reserveGapSize > maxGapLength && gapLength - reserveGapSize > buffer.Length / 4) { + // shrink gap + MakeNewBuffer(gapBeginOffset, reserveGapSize + minGapLength); + } + } + + void PlaceGap(int newGapOffset, int minRequiredGapLength) + { + if (gapLength < minRequiredGapLength) { + // enlarge gap + MakeNewBuffer(newGapOffset, minRequiredGapLength + Math.Max(minGapLength, buffer.Length / 8)); + } else { + while (newGapOffset < gapBeginOffset) { + buffer[--gapEndOffset] = buffer[--gapBeginOffset]; + } + while (newGapOffset > gapBeginOffset) { + buffer[gapBeginOffset++] = buffer[gapEndOffset++]; + } + } + } + + void MakeNewBuffer(int newGapOffset, int newGapLength) + { + char[] newBuffer = new char[Length + newGapLength]; + Debug.WriteLine("GapTextBuffer was reallocated, new size=" + newBuffer.Length); + if (newGapOffset < gapBeginOffset) { + // gap is moving backwards + + // first part: + Array.Copy(buffer, 0, newBuffer, 0, newGapOffset); + // moving middle part: + Array.Copy(buffer, newGapOffset, newBuffer, newGapOffset + newGapLength, gapBeginOffset - newGapOffset); + // last part: + Array.Copy(buffer, gapEndOffset, newBuffer, newBuffer.Length - (buffer.Length - gapEndOffset), buffer.Length - gapEndOffset); + } else { + // gap is moving forwards + // first part: + Array.Copy(buffer, 0, newBuffer, 0, gapBeginOffset); + // moving middle part: + Array.Copy(buffer, gapEndOffset, newBuffer, gapBeginOffset, newGapOffset - gapBeginOffset); + // last part: + int lastPartLength = newBuffer.Length - (newGapOffset + newGapLength); + Array.Copy(buffer, buffer.Length - lastPartLength, newBuffer, newGapOffset + newGapLength, lastPartLength); + } + + gapBeginOffset = newGapOffset; + gapEndOffset = newGapOffset + newGapLength; + gapLength = newGapLength; + buffer = newBuffer; + } + } + */ +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ILineTracker.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ILineTracker.cs new file mode 100644 index 000000000..c16128d63 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ILineTracker.cs @@ -0,0 +1,55 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Allows for low-level line tracking. + /// + /// + /// The methods on this interface are called by the TextDocument's LineManager immediately after the document + /// has changed, *while* the DocumentLineTree is updating. + /// Thus, the DocumentLineTree may be in an invalid state when these methods are called. + /// This interface should only be used to update per-line data structures like the HeightTree. + /// Line trackers must not cause any events to be raised during an update to prevent other code from seeing + /// the invalid state. + /// Line trackers may be called while the TextDocument has taken a lock. + /// You must be careful not to dead-lock inside ILineTracker callbacks. + /// + public interface ILineTracker + { + /// + /// Is called immediately before a document line is removed. + /// + void BeforeRemoveLine(DocumentLine line); + +// /// +// /// Is called immediately after a document line is removed. +// /// +// void AfterRemoveLine(DocumentLine line); + + /// + /// Is called immediately before a document line changes length. + /// This method will be called whenever the line is changed, even when the length stays as it is. + /// The method might be called multiple times for a single line because + /// a replacement is internally handled as removal followed by insertion. + /// + void SetLineLength(DocumentLine line, int newTotalLength); + + /// + /// Is called immediately after a line was inserted. + /// + /// The new line + /// The existing line before the new line + void LineInserted(DocumentLine insertionPos, DocumentLine newLine); + + /// + /// Indicates that there were changes to the document that the line tracker was not notified of. + /// The document is in a consistent state (but the line trackers aren't), and line trackers should + /// throw away their data and rebuild the document. + /// + void RebuildDocument(); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ISegment.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ISegment.cs new file mode 100644 index 000000000..80eb63352 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ISegment.cs @@ -0,0 +1,219 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using Tango.Scripting.Editors.Utils; +using System.Globalization; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// An (Offset,Length)-pair. + /// + /// + /// + public interface ISegment + { + /// + /// Gets the start offset of the segment. + /// + int Offset { get; } + + /// + /// Gets the length of the segment. + /// + /// Must not be negative. + int Length { get; } + + /// + /// Gets the end offset of the segment. + /// + /// EndOffset = Offset + Length; + int EndOffset { get; } + } + + static class SegmentExtensions + { + /// + /// Gets whether the segment contains the offset. + /// + /// + /// True, if offset is between segment.Start and segment.End (inclusive); otherwise, false. + /// + public static bool Contains(this ISegment segment, int offset) + { + int start = segment.Offset; + int end = start + segment.Length; + return offset >= start && offset <= end; + } + + /// + /// Gets the overlapping portion of the segments. + /// Returns SimpleSegment.Invalid if the segments don't overlap. + /// + public static SimpleSegment GetOverlap(this ISegment segment, ISegment other) + { + int start = Math.Max(segment.Offset, other.Offset); + int end = Math.Min(segment.EndOffset, other.EndOffset); + if (end < start) + return SimpleSegment.Invalid; + else + return new SimpleSegment(start, end - start); + } + } + + /// + /// Represents a simple segment (Offset,Length pair) that is not automatically updated + /// on document changes. + /// + struct SimpleSegment : IEquatable, ISegment + { + public static readonly SimpleSegment Invalid = new SimpleSegment(-1, -1); + + public readonly int Offset, Length; + + int ISegment.Offset { + get { return Offset; } + } + + int ISegment.Length { + get { return Length; } + } + + public int EndOffset { + get { + return Offset + Length; + } + } + + public SimpleSegment(int offset, int length) + { + this.Offset = offset; + this.Length = length; + } + + public SimpleSegment(ISegment segment) + { + Debug.Assert(segment != null); + this.Offset = segment.Offset; + this.Length = segment.Length; + } + + public override int GetHashCode() + { + unchecked { + return Offset + 10301 * Length; + } + } + + public override bool Equals(object obj) + { + return (obj is SimpleSegment) && Equals((SimpleSegment)obj); + } + + public bool Equals(SimpleSegment other) + { + return this.Offset == other.Offset && this.Length == other.Length; + } + + public static bool operator ==(SimpleSegment left, SimpleSegment right) + { + return left.Equals(right); + } + + public static bool operator !=(SimpleSegment left, SimpleSegment right) + { + return !left.Equals(right); + } + + public override string ToString() + { + return "[Offset=" + Offset.ToString(CultureInfo.InvariantCulture) + ", Length=" + Length.ToString(CultureInfo.InvariantCulture) + "]"; + } + } + + /// + /// A segment using s as start and end positions. + /// + /// + /// + /// For the constructors creating new anchors, the start position will be AfterInsertion and the end position will be BeforeInsertion. + /// Should the end position move before the start position, the segment will have length 0. + /// + /// + /// + /// + public sealed class AnchorSegment : ISegment + { + readonly TextAnchor start, end; + + /// + public int Offset { + get { return start.Offset; } + } + + /// + public int Length { + get { + // Math.Max takes care of the fact that end.Offset might move before start.Offset. + return Math.Max(0, end.Offset - start.Offset); + } + } + + /// + public int EndOffset { + get { + // Math.Max takes care of the fact that end.Offset might move before start.Offset. + return Math.Max(start.Offset, end.Offset); + } + } + + /// + /// Creates a new AnchorSegment using the specified anchors. + /// The anchors must have set to true. + /// + public AnchorSegment(TextAnchor start, TextAnchor end) + { + if (start == null) + throw new ArgumentNullException("start"); + if (end == null) + throw new ArgumentNullException("end"); + if (!start.SurviveDeletion) + throw new ArgumentException("Anchors for AnchorSegment must use SurviveDeletion", "start"); + if (!end.SurviveDeletion) + throw new ArgumentException("Anchors for AnchorSegment must use SurviveDeletion", "end"); + this.start = start; + this.end = end; + } + + /// + /// Creates a new AnchorSegment that creates new anchors. + /// + public AnchorSegment(TextDocument document, ISegment segment) + : this(document, ThrowUtil.CheckNotNull(segment, "segment").Offset, segment.Length) + { + } + + /// + /// Creates a new AnchorSegment that creates new anchors. + /// + public AnchorSegment(TextDocument document, int offset, int length) + { + if (document == null) + throw new ArgumentNullException("document"); + this.start = document.CreateAnchor(offset); + this.start.SurviveDeletion = true; + this.start.MovementType = AnchorMovementType.AfterInsertion; + this.end = document.CreateAnchor(offset + length); + this.end.SurviveDeletion = true; + this.end.MovementType = AnchorMovementType.BeforeInsertion; + } + + /// + public override string ToString() + { + return "[Offset=" + Offset.ToString(CultureInfo.InvariantCulture) + ", EndOffset=" + EndOffset.ToString(CultureInfo.InvariantCulture) + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ITextSource.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ITextSource.cs new file mode 100644 index 000000000..c52a333c5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/ITextSource.cs @@ -0,0 +1,320 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; +using System.IO; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Interface for read-only access to a text source. + /// + /// + /// + public interface ITextSource + { + /// + /// Gets the whole text as string. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + string Text { get; } + + /// + /// Is raised when the Text property changes. + /// + event EventHandler TextChanged; + + /// + /// Gets the total text length. + /// + /// The length of the text, in characters. + /// This is the same as Text.Length, but is more efficient because + /// it doesn't require creating a String object. + int TextLength { get; } + + /// + /// Gets a character at the specified position in the document. + /// + /// The index of the character to get. + /// Offset is outside the valid range (0 to TextLength-1). + /// The character at the specified position. + /// This is the same as Text[offset], but is more efficient because + /// it doesn't require creating a String object. + char GetCharAt(int offset); + + /// + /// Gets the index of the first occurrence of any character in the specified array. + /// + /// + /// Start index of the search. + /// Length of the area to search. + /// The first index where any character was found; or -1 if no occurrence was found. + int IndexOfAny(char[] anyOf, int startIndex, int count); + + /// + /// Retrieves the text for a portion of the document. + /// + /// offset or length is outside the valid range. + /// This is the same as Text.Substring, but is more efficient because + /// it doesn't require creating a String object for the whole document. + string GetText(int offset, int length); + + /// + /// Creates a snapshot of the current text. + /// + /// + /// This method is generally not thread-safe when called on a mutable text buffer, but the resulting text buffer is immutable and thread-safe. + /// However, some implementing classes may provide additional thread-safety guarantees, see TextDocument.CreateSnapshot. + /// + ITextSource CreateSnapshot(); + + /// + /// Creates a snapshot of a part of the current text. + /// + /// + /// This method is generally not thread-safe when called on a mutable text buffer, but the resulting text buffer is immutable and thread-safe. + /// However, some implementing classes may provide additional thread-safety guarantees, see TextDocument.CreateSnapshot. + /// + ITextSource CreateSnapshot(int offset, int length); + + /// + /// Creates a text reader. + /// If the text is changed while a reader is active, the reader will continue to read from the old text version. + /// + TextReader CreateReader(); + } + + /// + /// Implements the ITextSource interface by wrapping another TextSource + /// and viewing only a part of the text. + /// + [Obsolete("This class will be removed in a future version of AvalonEdit")] + public sealed class TextSourceView : ITextSource + { + readonly ITextSource baseTextSource; + readonly ISegment viewedSegment; + + /// + /// Creates a new TextSourceView object. + /// + /// The base text source. + /// A text segment from the base text source + public TextSourceView(ITextSource baseTextSource, ISegment viewedSegment) + { + if (baseTextSource == null) + throw new ArgumentNullException("baseTextSource"); + if (viewedSegment == null) + throw new ArgumentNullException("viewedSegment"); + this.baseTextSource = baseTextSource; + this.viewedSegment = viewedSegment; + } + + /// + public event EventHandler TextChanged { + add { baseTextSource.TextChanged += value; } + remove { baseTextSource.TextChanged -= value; } + } + + /// + public string Text { + get { + return baseTextSource.GetText(viewedSegment.Offset, viewedSegment.Length); + } + } + + /// + public int TextLength { + get { return viewedSegment.Length; } + } + + /// + public char GetCharAt(int offset) + { + return baseTextSource.GetCharAt(viewedSegment.Offset + offset); + } + + /// + public string GetText(int offset, int length) + { + return baseTextSource.GetText(viewedSegment.Offset + offset, length); + } + + /// + public ITextSource CreateSnapshot() + { + return baseTextSource.CreateSnapshot(viewedSegment.Offset, viewedSegment.Length); + } + + /// + public ITextSource CreateSnapshot(int offset, int length) + { + return baseTextSource.CreateSnapshot(viewedSegment.Offset + offset, length); + } + + /// + public TextReader CreateReader() + { + return CreateSnapshot().CreateReader(); + } + + /// + public int IndexOfAny(char[] anyOf, int startIndex, int count) + { + int offset = viewedSegment.Offset; + int result = baseTextSource.IndexOfAny(anyOf, startIndex + offset, count); + return result >= 0 ? result - offset : result; + } + } + + /// + /// Implements the ITextSource interface using a string. + /// + public sealed class StringTextSource : ITextSource + { + readonly string text; + + /// + /// Creates a new StringTextSource. + /// + public StringTextSource(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + this.text = text; + } + + // Text can never change + event EventHandler ITextSource.TextChanged { add {} remove {} } + + /// + public string Text { + get { return text; } + } + + /// + public int TextLength { + get { return text.Length; } + } + + /// + public char GetCharAt(int offset) + { + // GetCharAt must throw ArgumentOutOfRangeException, not IndexOutOfRangeException + if (offset < 0 || offset >= text.Length) + throw new ArgumentOutOfRangeException("offset", offset, "offset must be between 0 and " + (text.Length - 1)); + return text[offset]; + } + + /// + public string GetText(int offset, int length) + { + return text.Substring(offset, length); + } + + /// + public TextReader CreateReader() + { + return new StringReader(text); + } + + /// + public ITextSource CreateSnapshot() + { + return this; // StringTextSource already is immutable + } + + /// + public ITextSource CreateSnapshot(int offset, int length) + { + return new StringTextSource(text.Substring(offset, length)); + } + + /// + public int IndexOfAny(char[] anyOf, int startIndex, int count) + { + return text.IndexOfAny(anyOf, startIndex, count); + } + } + + /// + /// Implements the ITextSource interface using a rope. + /// + public sealed class RopeTextSource : ITextSource + { + readonly Rope rope; + + /// + /// Creates a new RopeTextSource. + /// + public RopeTextSource(Rope rope) + { + if (rope == null) + throw new ArgumentNullException("rope"); + this.rope = rope; + } + + /// + /// Returns a clone of the rope used for this text source. + /// + /// + /// RopeTextSource only publishes a copy of the contained rope to ensure that the underlying rope cannot be modified. + /// Unless the creator of the RopeTextSource still has a reference on the rope, RopeTextSource is immutable. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Not a property because it creates a clone")] + public Rope GetRope() + { + return rope.Clone(); + } + + // Change event is not supported + event EventHandler ITextSource.TextChanged { add {} remove {} } + + /// + public string Text { + get { return rope.ToString(); } + } + + /// + public int TextLength { + get { return rope.Length; } + } + + /// + public char GetCharAt(int offset) + { + return rope[offset]; + } + + /// + public string GetText(int offset, int length) + { + return rope.ToString(offset, length); + } + + /// + public TextReader CreateReader() + { + return new RopeTextReader(rope); + } + + /// + public ITextSource CreateSnapshot() + { + // we clone the underlying rope because the creator of the RopeTextSource might be modifying it + return new RopeTextSource(rope.Clone()); + } + + /// + public ITextSource CreateSnapshot(int offset, int length) + { + return new RopeTextSource(rope.GetRange(offset, length)); + } + + /// + public int IndexOfAny(char[] anyOf, int startIndex, int count) + { + return rope.IndexOfAny(anyOf, startIndex, count); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/IUndoableOperation.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/IUndoableOperation.cs new file mode 100644 index 000000000..2e19c462c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/IUndoableOperation.cs @@ -0,0 +1,30 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// This Interface describes a the basic Undo/Redo operation + /// all Undo Operations must implement this interface. + /// + public interface IUndoableOperation + { + /// + /// Undo the last operation + /// + void Undo(); + + /// + /// Redo the last operation + /// + void Redo(); + } + + interface IUndoableOperationWithContext : IUndoableOperation + { + void Undo(UndoStack stack); + void Redo(UndoStack stack); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineManager.cs new file mode 100644 index 000000000..ecab1fa48 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineManager.cs @@ -0,0 +1,288 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Creates/Deletes lines when text is inserted/removed. + /// + sealed class LineManager + { + #region Constructor + readonly TextDocument document; + readonly DocumentLineTree documentLineTree; + + /// + /// A copy of the line trackers. We need a copy so that line trackers may remove themselves + /// while being notified (used e.g. by WeakLineTracker) + /// + ILineTracker[] lineTrackers; + + internal void UpdateListOfLineTrackers() + { + this.lineTrackers = document.LineTrackers.ToArray(); + } + + public LineManager(DocumentLineTree documentLineTree, TextDocument document) + { + this.document = document; + this.documentLineTree = documentLineTree; + UpdateListOfLineTrackers(); + + Rebuild(); + } + #endregion + + #region Change events + /* + HashSet deletedLines = new HashSet(); + readonly HashSet changedLines = new HashSet(); + HashSet deletedOrChangedLines = new HashSet(); + + /// + /// Gets the list of lines deleted since the last RetrieveChangedLines() call. + /// The returned list is unsorted. + /// + public ICollection RetrieveDeletedLines() + { + var r = deletedLines; + deletedLines = new HashSet(); + return r; + } + + /// + /// Gets the list of lines changed since the last RetrieveChangedLines() call. + /// The returned list is sorted by line number and does not contain deleted lines. + /// + public List RetrieveChangedLines() + { + var list = (from line in changedLines + where !line.IsDeleted + let number = line.LineNumber + orderby number + select line).ToList(); + changedLines.Clear(); + return list; + } + + /// + /// Gets the list of lines changed since the last RetrieveDeletedOrChangedLines() call. + /// The returned list is not sorted. + /// + public ICollection RetrieveDeletedOrChangedLines() + { + var r = deletedOrChangedLines; + deletedOrChangedLines = new HashSet(); + return r; + } + */ + #endregion + + #region Rebuild + public void Rebuild() + { + // keep the first document line + DocumentLine ls = documentLineTree.GetByNumber(1); + SimpleSegment ds = NewLineFinder.NextNewLine(document, 0); + List lines = new List(); + int lastDelimiterEnd = 0; + while (ds != SimpleSegment.Invalid) { + ls.TotalLength = ds.Offset + ds.Length - lastDelimiterEnd; + ls.DelimiterLength = ds.Length; + lastDelimiterEnd = ds.Offset + ds.Length; + lines.Add(ls); + + ls = new DocumentLine(document); + ds = NewLineFinder.NextNewLine(document, lastDelimiterEnd); + } + ls.ResetLine(); + ls.TotalLength = document.TextLength - lastDelimiterEnd; + lines.Add(ls); + documentLineTree.RebuildTree(lines); + foreach (ILineTracker lineTracker in lineTrackers) + lineTracker.RebuildDocument(); + } + #endregion + + #region Remove + public void Remove(int offset, int length) + { + Debug.Assert(length >= 0); + if (length == 0) return; + DocumentLine startLine = documentLineTree.GetByOffset(offset); + int startLineOffset = startLine.Offset; + + Debug.Assert(offset < startLineOffset + startLine.TotalLength); + if (offset > startLineOffset + startLine.Length) { + Debug.Assert(startLine.DelimiterLength == 2); + // we are deleting starting in the middle of a delimiter + + // remove last delimiter part + SetLineLength(startLine, startLine.TotalLength - 1); + // remove remaining text + Remove(offset, length - 1); + return; + } + + if (offset + length < startLineOffset + startLine.TotalLength) { + // just removing a part of this line + //startLine.RemovedLinePart(ref deferredEventList, offset - startLineOffset, length); + SetLineLength(startLine, startLine.TotalLength - length); + return; + } + // merge startLine with another line because startLine's delimiter was deleted + // possibly remove lines in between if multiple delimiters were deleted + int charactersRemovedInStartLine = startLineOffset + startLine.TotalLength - offset; + Debug.Assert(charactersRemovedInStartLine > 0); + //startLine.RemovedLinePart(ref deferredEventList, offset - startLineOffset, charactersRemovedInStartLine); + + + DocumentLine endLine = documentLineTree.GetByOffset(offset + length); + if (endLine == startLine) { + // special case: we are removing a part of the last line up to the + // end of the document + SetLineLength(startLine, startLine.TotalLength - length); + return; + } + int endLineOffset = endLine.Offset; + int charactersLeftInEndLine = endLineOffset + endLine.TotalLength - (offset + length); + //endLine.RemovedLinePart(ref deferredEventList, 0, endLine.TotalLength - charactersLeftInEndLine); + //startLine.MergedWith(endLine, offset - startLineOffset); + + // remove all lines between startLine (excl.) and endLine (incl.) + DocumentLine tmp = startLine.NextLine; + DocumentLine lineToRemove; + do { + lineToRemove = tmp; + tmp = tmp.NextLine; + RemoveLine(lineToRemove); + } while (lineToRemove != endLine); + + SetLineLength(startLine, startLine.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine); + } + + void RemoveLine(DocumentLine lineToRemove) + { + foreach (ILineTracker lt in lineTrackers) + lt.BeforeRemoveLine(lineToRemove); + documentLineTree.RemoveLine(lineToRemove); +// foreach (ILineTracker lt in lineTracker) +// lt.AfterRemoveLine(lineToRemove); +// deletedLines.Add(lineToRemove); +// deletedOrChangedLines.Add(lineToRemove); + } + + #endregion + + #region Insert + public void Insert(int offset, string text) + { + DocumentLine line = documentLineTree.GetByOffset(offset); + int lineOffset = line.Offset; + + Debug.Assert(offset <= lineOffset + line.TotalLength); + if (offset > lineOffset + line.Length) { + Debug.Assert(line.DelimiterLength == 2); + // we are inserting in the middle of a delimiter + + // shorten line + SetLineLength(line, line.TotalLength - 1); + // add new line + line = InsertLineAfter(line, 1); + line = SetLineLength(line, 1); + } + + SimpleSegment ds = NewLineFinder.NextNewLine(text, 0); + if (ds == SimpleSegment.Invalid) { + // no newline is being inserted, all text is inserted in a single line + //line.InsertedLinePart(offset - line.Offset, text.Length); + SetLineLength(line, line.TotalLength + text.Length); + return; + } + //DocumentLine firstLine = line; + //firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset); + int lastDelimiterEnd = 0; + while (ds != SimpleSegment.Invalid) { + // split line segment at line delimiter + int lineBreakOffset = offset + ds.Offset + ds.Length; + lineOffset = line.Offset; + int lengthAfterInsertionPos = lineOffset + line.TotalLength - (offset + lastDelimiterEnd); + line = SetLineLength(line, lineBreakOffset - lineOffset); + DocumentLine newLine = InsertLineAfter(line, lengthAfterInsertionPos); + newLine = SetLineLength(newLine, lengthAfterInsertionPos); + + line = newLine; + lastDelimiterEnd = ds.Offset + ds.Length; + + ds = NewLineFinder.NextNewLine(text, lastDelimiterEnd); + } + //firstLine.SplitTo(line); + // insert rest after last delimiter + if (lastDelimiterEnd != text.Length) { + //line.InsertedLinePart(0, text.Length - lastDelimiterEnd); + SetLineLength(line, line.TotalLength + text.Length - lastDelimiterEnd); + } + } + + DocumentLine InsertLineAfter(DocumentLine line, int length) + { + DocumentLine newLine = documentLineTree.InsertLineAfter(line, length); + foreach (ILineTracker lt in lineTrackers) + lt.LineInserted(line, newLine); + return newLine; + } + #endregion + + #region SetLineLength + /// + /// Sets the total line length and checks the delimiter. + /// This method can cause line to be deleted when it contains a single '\n' character + /// and the previous line ends with '\r'. + /// + /// Usually returns , but if line was deleted due to + /// the "\r\n" merge, returns the previous line. + DocumentLine SetLineLength(DocumentLine line, int newTotalLength) + { +// changedLines.Add(line); +// deletedOrChangedLines.Add(line); + int delta = newTotalLength - line.TotalLength; + if (delta != 0) { + foreach (ILineTracker lt in lineTrackers) + lt.SetLineLength(line, newTotalLength); + line.TotalLength = newTotalLength; + DocumentLineTree.UpdateAfterChildrenChange(line); + } + // determine new DelimiterLength + if (newTotalLength == 0) { + line.DelimiterLength = 0; + } else { + int lineOffset = line.Offset; + char lastChar = document.GetCharAt(lineOffset + newTotalLength - 1); + if (lastChar == '\r') { + line.DelimiterLength = 1; + } else if (lastChar == '\n') { + if (newTotalLength >= 2 && document.GetCharAt(lineOffset + newTotalLength - 2) == '\r') { + line.DelimiterLength = 2; + } else if (newTotalLength == 1 && lineOffset > 0 && document.GetCharAt(lineOffset - 1) == '\r') { + // we need to join this line with the previous line + DocumentLine previousLine = line.PreviousLine; + RemoveLine(line); + return SetLineLength(previousLine, previousLine.TotalLength + 1); + } else { + line.DelimiterLength = 1; + } + } else { + line.DelimiterLength = 0; + } + } + return line; + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineNode.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineNode.cs new file mode 100644 index 000000000..317202721 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/LineNode.cs @@ -0,0 +1,83 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Document +{ + using LineNode = DocumentLine; + + // A tree node in the document line tree. + // For the purpose of the invariants, "children", "descendents", "siblings" etc. include the DocumentLine object, + // it is treated as a third child node between left and right. + + // Originally, this was a separate class, with a reference to the documentLine. The documentLine had a reference + // back to the node. To save memory, the same object is used for both the documentLine and the line node. + // This saves 16 bytes per line (8 byte object overhead + two pointers). +// sealed class LineNode +// { +// internal readonly DocumentLine documentLine; + partial class DocumentLine + { + internal DocumentLine left, right, parent; + internal bool color; + // optimization note: I tried packing color and isDeleted into a single byte field, but that + // actually increased the memory requirements. The JIT packs two bools and a byte (delimiterSize) + // into a single DWORD, but two bytes get each their own DWORD. Three bytes end up in the same DWORD, so + // apparently the JIT only optimizes for memory when there are at least three small fields. + // Currently, DocumentLine takes 36 bytes on x86 (8 byte object overhead, 3 pointers, 3 ints, and another DWORD + // for the small fields). + // TODO: a possible optimization would be to combine 'totalLength' and the small fields into a single uint. + // delimiterSize takes only two bits, the two bools take another two bits; so there's still + // 28 bits left for totalLength. 268435455 characters per line should be enough for everyone :) + + /// + /// Resets the line to enable its reuse after a document rebuild. + /// + internal void ResetLine() + { + totalLength = delimiterLength = 0; + isDeleted = color = false; + left = right = parent = null; + } + + internal LineNode InitLineNode() + { + this.nodeTotalCount = 1; + this.nodeTotalLength = this.TotalLength; + return this; + } + + internal LineNode LeftMost { + get { + LineNode node = this; + while (node.left != null) + node = node.left; + return node; + } + } + + internal LineNode RightMost { + get { + LineNode node = this; + while (node.right != null) + node = node.right; + return node; + } + } + + /// + /// The number of lines in this node and its child nodes. + /// Invariant: + /// nodeTotalCount = 1 + left.nodeTotalCount + right.nodeTotalCount + /// + internal int nodeTotalCount; + + /// + /// The total text length of this node and its child nodes. + /// Invariant: + /// nodeTotalLength = left.nodeTotalLength + documentLine.TotalLength + right.nodeTotalLength + /// + internal int nodeTotalLength; + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/NewLineFinder.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/NewLineFinder.cs new file mode 100644 index 000000000..6ecf8c1e5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/NewLineFinder.cs @@ -0,0 +1,134 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Tango.Scripting.Editors.Document +{ + static class NewLineFinder + { + static readonly char[] newline = { '\r', '\n' }; + + internal static readonly string[] NewlineStrings = { "\r\n", "\r", "\n" }; + + /// + /// Gets the location of the next new line character, or SimpleSegment.Invalid + /// if none is found. + /// + internal static SimpleSegment NextNewLine(string text, int offset) + { + int pos = text.IndexOfAny(newline, offset); + if (pos >= 0) { + if (text[pos] == '\r') { + if (pos + 1 < text.Length && text[pos + 1] == '\n') + return new SimpleSegment(pos, 2); + } + return new SimpleSegment(pos, 1); + } + return SimpleSegment.Invalid; + } + + /// + /// Gets the location of the next new line character, or SimpleSegment.Invalid + /// if none is found. + /// + internal static SimpleSegment NextNewLine(ITextSource text, int offset) + { + int textLength = text.TextLength; + int pos = text.IndexOfAny(newline, offset, textLength - offset); + if (pos >= 0) { + if (text.GetCharAt(pos) == '\r') { + if (pos + 1 < textLength && text.GetCharAt(pos + 1) == '\n') + return new SimpleSegment(pos, 2); + } + return new SimpleSegment(pos, 1); + } + return SimpleSegment.Invalid; + } + } + + partial class TextUtilities + { + /// + /// Finds the next new line character starting at offset. + /// + /// The text source to search in. + /// The starting offset for the search. + /// The string representing the new line that was found, or null if no new line was found. + /// The position of the first new line starting at or after , + /// or -1 if no new line was found. + public static int FindNextNewLine(ITextSource text, int offset, out string newLineType) + { + if (text == null) + throw new ArgumentNullException("text"); + if (offset < 0 || offset > text.TextLength) + throw new ArgumentOutOfRangeException("offset", offset, "offset is outside of text source"); + SimpleSegment s = NewLineFinder.NextNewLine(text, offset); + if (s == SimpleSegment.Invalid) { + newLineType = null; + return -1; + } else { + if (s.Length == 2) { + newLineType = "\r\n"; + } else if (text.GetCharAt(s.Offset) == '\n') { + newLineType = "\n"; + } else { + newLineType = "\r"; + } + return s.Offset; + } + } + + /// + /// Gets whether the specified string is a newline sequence. + /// + public static bool IsNewLine(string newLine) + { + return newLine == "\r\n" || newLine == "\n" || newLine == "\r"; + } + + /// + /// Normalizes all new lines in to be . + /// + public static string NormalizeNewLines(string input, string newLine) + { + if (input == null) + return null; + if (!IsNewLine(newLine)) + throw new ArgumentException("newLine must be one of the known newline sequences"); + SimpleSegment ds = NewLineFinder.NextNewLine(input, 0); + if (ds == SimpleSegment.Invalid) // text does not contain any new lines + return input; + StringBuilder b = new StringBuilder(input.Length); + int lastEndOffset = 0; + do { + b.Append(input, lastEndOffset, ds.Offset - lastEndOffset); + b.Append(newLine); + lastEndOffset = ds.EndOffset; + ds = NewLineFinder.NextNewLine(input, lastEndOffset); + } while (ds != SimpleSegment.Invalid); + // remaining string (after last newline) + b.Append(input, lastEndOffset, input.Length - lastEndOffset); + return b.ToString(); + } + + /// + /// Gets the newline sequence used in the document at the specified line. + /// + public static string GetNewLineFromDocument(TextDocument document, int lineNumber) + { + DocumentLine line = document.GetLineByNumber(lineNumber); + if (line.DelimiterLength == 0) { + // at the end of the document, there's no line delimiter, so use the delimiter + // from the previous line + line = line.PreviousLine; + if (line == null) + return Environment.NewLine; + } + return document.GetText(line.Offset + line.Length, line.DelimiterLength); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/OffsetChangeMap.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/OffsetChangeMap.cs new file mode 100644 index 000000000..9494af56b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/OffsetChangeMap.cs @@ -0,0 +1,347 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; + +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Contains predefined offset change mapping types. + /// + public enum OffsetChangeMappingType + { + /// + /// Normal replace. + /// Anchors in front of the replaced region will stay in front, anchors after the replaced region will stay after. + /// Anchors in the middle of the removed region will be deleted. If they survive deletion, + /// they move depending on their AnchorMovementType. + /// + /// + /// This is the default implementation of DocumentChangeEventArgs when OffsetChangeMap is null, + /// so using this option usually works without creating an OffsetChangeMap instance. + /// This is equivalent to an OffsetChangeMap with a single entry describing the replace operation. + /// + Normal, + /// + /// First the old text is removed, then the new text is inserted. + /// Anchors immediately in front (or after) the replaced region may move to the other side of the insertion, + /// depending on the AnchorMovementType. + /// + /// + /// This is implemented as an OffsetChangeMap with two entries: the removal, and the insertion. + /// + RemoveAndInsert, + /// + /// The text is replaced character-by-character. + /// Anchors keep their position inside the replaced text. + /// Anchors after the replaced region will move accordingly if the replacement text has a different length than the replaced text. + /// If the new text is shorter than the old text, anchors inside the old text that would end up behind the replacement text + /// will be moved so that they point to the end of the replacement text. + /// + /// + /// On the OffsetChangeMap level, growing text is implemented by replacing the last character in the replaced text + /// with itself and the additional text segment. A simple insertion of the additional text would have the undesired + /// effect of moving anchors immediately after the replaced text into the replacement text if they used + /// AnchorMovementStyle.BeforeInsertion. + /// Shrinking text is implemented by removing the text segment that's too long; but in a special mode that + /// causes anchors to always survive irrespective of their setting. + /// If the text keeps its old size, this is implemented as OffsetChangeMap.Empty. + /// + CharacterReplace, + /// + /// Like 'Normal', but anchors with = Default will stay in front of the + /// insertion instead of being moved behind it. + /// + KeepAnchorBeforeInsertion + } + + /// + /// Describes a series of offset changes. + /// + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification="It's a mapping old offsets -> new offsets")] + public sealed class OffsetChangeMap : Collection + { + /// + /// Immutable OffsetChangeMap that is empty. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", + Justification="The Empty instance is immutable")] + public static readonly OffsetChangeMap Empty = new OffsetChangeMap(Empty.Array, true); + + /// + /// Creates a new OffsetChangeMap with a single element. + /// + /// The entry. + /// Returns a frozen OffsetChangeMap with a single entry. + public static OffsetChangeMap FromSingleElement(OffsetChangeMapEntry entry) + { + return new OffsetChangeMap(new OffsetChangeMapEntry[] { entry }, true); + } + + bool isFrozen; + + /// + /// Creates a new OffsetChangeMap instance. + /// + public OffsetChangeMap() + { + } + + internal OffsetChangeMap(int capacity) + : base(new List(capacity)) + { + } + + private OffsetChangeMap(IList entries, bool isFrozen) + : base(entries) + { + this.isFrozen = isFrozen; + } + + /// + /// Gets the new offset where the specified offset moves after this document change. + /// + public int GetNewOffset(int offset, AnchorMovementType movementType) + { + IList items = this.Items; + int count = items.Count; + for (int i = 0; i < count; i++) { + offset = items[i].GetNewOffset(offset, movementType); + } + return offset; + } + + /// + /// Gets whether this OffsetChangeMap is a valid explanation for the specified document change. + /// + public bool IsValidForDocumentChange(int offset, int removalLength, int insertionLength) + { + int endOffset = offset + removalLength; + foreach (OffsetChangeMapEntry entry in this) { + // check that ChangeMapEntry is in valid range for this document change + if (entry.Offset < offset || entry.Offset + entry.RemovalLength > endOffset) + return false; + endOffset += entry.InsertionLength - entry.RemovalLength; + } + // check that the total delta matches + return endOffset == offset + insertionLength; + } + + /// + /// Calculates the inverted OffsetChangeMap (used for the undo operation). + /// + public OffsetChangeMap Invert() + { + if (this == Empty) + return this; + OffsetChangeMap newMap = new OffsetChangeMap(this.Count); + for (int i = this.Count - 1; i >= 0; i--) { + OffsetChangeMapEntry entry = this[i]; + // swap InsertionLength and RemovalLength + newMap.Add(new OffsetChangeMapEntry(entry.Offset, entry.InsertionLength, entry.RemovalLength)); + } + return newMap; + } + + /// + protected override void ClearItems() + { + CheckFrozen(); + base.ClearItems(); + } + + /// + protected override void InsertItem(int index, OffsetChangeMapEntry item) + { + CheckFrozen(); + base.InsertItem(index, item); + } + + /// + protected override void RemoveItem(int index) + { + CheckFrozen(); + base.RemoveItem(index); + } + + /// + protected override void SetItem(int index, OffsetChangeMapEntry item) + { + CheckFrozen(); + base.SetItem(index, item); + } + + void CheckFrozen() + { + if (isFrozen) + throw new InvalidOperationException("This instance is frozen and cannot be modified."); + } + + /// + /// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe. + /// + public bool IsFrozen { + get { return isFrozen; } + } + + /// + /// Freezes this instance. + /// + public void Freeze() + { + isFrozen = true; + } + } + + /// + /// An entry in the OffsetChangeMap. + /// This represents the offset of a document change (either insertion or removal, not both at once). + /// + [Serializable] + public struct OffsetChangeMapEntry : IEquatable + { + readonly int offset; + + // MSB: DefaultAnchorMovementIsBeforeInsertion + readonly uint insertionLengthWithMovementFlag; + + // MSB: RemovalNeverCausesAnchorDeletion; other 31 bits: RemovalLength + readonly uint removalLengthWithDeletionFlag; + + /// + /// The offset at which the change occurs. + /// + public int Offset { + get { return offset; } + } + + /// + /// The number of characters inserted. + /// Returns 0 if this entry represents a removal. + /// + public int InsertionLength { + get { return (int)(insertionLengthWithMovementFlag & 0x7fffffff); } + } + + /// + /// The number of characters removed. + /// Returns 0 if this entry represents an insertion. + /// + public int RemovalLength { + get { return (int)(removalLengthWithDeletionFlag & 0x7fffffff); } + } + + /// + /// Gets whether the removal should not cause any anchor deletions. + /// + public bool RemovalNeverCausesAnchorDeletion { + get { return (removalLengthWithDeletionFlag & 0x80000000) != 0; } + } + + /// + /// Gets whether default anchor movement causes the anchor to stay in front of the caret. + /// + public bool DefaultAnchorMovementIsBeforeInsertion { + get { return (insertionLengthWithMovementFlag & 0x80000000) != 0; } + } + + /// + /// Gets the new offset where the specified offset moves after this document change. + /// + public int GetNewOffset(int oldOffset, AnchorMovementType movementType) + { + int insertionLength = this.InsertionLength; + int removalLength = this.RemovalLength; + if (!(removalLength == 0 && oldOffset == offset)) { + // we're getting trouble (both if statements in here would apply) + // if there's no removal and we insert at the offset + // -> we'd need to disambiguate by movementType, which is handled after the if + + // offset is before start of change: no movement + if (oldOffset <= offset) + return oldOffset; + // offset is after end of change: movement by normal delta + if (oldOffset >= offset + removalLength) + return oldOffset + insertionLength - removalLength; + } + // we reach this point if + // a) the oldOffset is inside the deleted segment + // b) there was no removal and we insert at the caret position + if (movementType == AnchorMovementType.AfterInsertion) + return offset + insertionLength; + else if (movementType == AnchorMovementType.BeforeInsertion) + return offset; + else + return this.DefaultAnchorMovementIsBeforeInsertion ? offset : offset + insertionLength; + } + + /// + /// Creates a new OffsetChangeMapEntry instance. + /// + public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength) + { + ThrowUtil.CheckNotNegative(offset, "offset"); + ThrowUtil.CheckNotNegative(removalLength, "removalLength"); + ThrowUtil.CheckNotNegative(insertionLength, "insertionLength"); + + this.offset = offset; + this.removalLengthWithDeletionFlag = (uint)removalLength; + this.insertionLengthWithMovementFlag = (uint)insertionLength; + } + + /// + /// Creates a new OffsetChangeMapEntry instance. + /// + public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength, bool removalNeverCausesAnchorDeletion, bool defaultAnchorMovementIsBeforeInsertion) + : this(offset, removalLength, insertionLength) + { + if (removalNeverCausesAnchorDeletion) + this.removalLengthWithDeletionFlag |= 0x80000000; + if (defaultAnchorMovementIsBeforeInsertion) + this.insertionLengthWithMovementFlag |= 0x80000000; + } + + /// + public override int GetHashCode() + { + unchecked { + return offset + 3559 * (int)insertionLengthWithMovementFlag + 3571 * (int)removalLengthWithDeletionFlag; + } + } + + /// + public override bool Equals(object obj) + { + return obj is OffsetChangeMapEntry && this.Equals((OffsetChangeMapEntry)obj); + } + + /// + public bool Equals(OffsetChangeMapEntry other) + { + return offset == other.offset && insertionLengthWithMovementFlag == other.insertionLengthWithMovementFlag && removalLengthWithDeletionFlag == other.removalLengthWithDeletionFlag; + } + + /// + /// Tests the two entries for equality. + /// + public static bool operator ==(OffsetChangeMapEntry left, OffsetChangeMapEntry right) + { + return left.Equals(right); + } + + /// + /// Tests the two entries for inequality. + /// + public static bool operator !=(OffsetChangeMapEntry left, OffsetChangeMapEntry right) + { + return !left.Equals(right); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchor.cs new file mode 100644 index 000000000..a65c276f9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchor.cs @@ -0,0 +1,194 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// The TextAnchor class references an offset (a position between two characters). + /// It automatically updates the offset when text is inserted/removed in front of the anchor. + /// + /// + /// Use the property to get the offset from a text anchor. + /// Use the method to create an anchor from an offset. + /// + /// + /// The document will automatically update all text anchors; and because it uses weak references to do so, + /// the garbage collector can simply collect the anchor object when you don't need it anymore. + /// + /// Moreover, the document is able to efficiently update a large number of anchors without having to look + /// at each anchor object individually. Updating the offsets of all anchors usually only takes time logarithmic + /// to the number of anchors. Retrieving the property also runs in O(lg N). + /// + /// + /// If you want to track a segment, you can use the class which + /// implements using two text anchors. + /// + /// + /// Usage: + /// TextAnchor anchor = document.CreateAnchor(offset); + /// ChangeMyDocument(); + /// int newOffset = anchor.Offset; + /// + /// + public sealed class TextAnchor + { + readonly TextDocument document; + internal TextAnchorNode node; + + internal TextAnchor(TextDocument document) + { + this.document = document; + } + + /// + /// Gets the document owning the anchor. + /// + public TextDocument Document { + get { return document; } + } + + /// + /// Controls how the anchor moves. + /// + /// Anchor movement is ambiguous if text is inserted exactly at the anchor's location. + /// Does the anchor stay before the inserted text, or does it move after it? + /// The property will be used to determine which of these two options the anchor will choose. + /// The default value is . + public AnchorMovementType MovementType { get; set; } + + /// + /// + /// Specifies whether the anchor survives deletion of the text containing it. + /// + /// false: The anchor is deleted when the a selection that includes the anchor is deleted. + /// true: The anchor is not deleted. + /// + /// + /// + public bool SurviveDeletion { get; set; } + + /// + /// Gets whether the anchor was deleted. + /// + /// + /// When a piece of text containing an anchor is removed, then that anchor will be deleted. + /// First, the property is set to true on all deleted anchors, + /// then the events are raised. + /// You cannot retrieve the offset from an anchor that has been deleted. + /// This deletion behavior might be useful when using anchors for building a bookmark feature, + /// but in other cases you want to still be able to use the anchor. For those cases, set = true. + /// + public bool IsDeleted { + get { + document.DebugVerifyAccess(); + return node == null; + } + } + + /// + /// Occurs after the anchor was deleted. + /// + /// + /// + /// Due to the 'weak reference' nature of TextAnchor, you will receive the Deleted event only + /// while your code holds a reference to the TextAnchor object. + /// + public event EventHandler Deleted; + + internal void OnDeleted(DelayedEvents delayedEvents) + { + node = null; + delayedEvents.DelayedRaise(Deleted, this, EventArgs.Empty); + } + + /// + /// Gets the offset of the text anchor. + /// + /// Thrown when trying to get the Offset from a deleted anchor. + public int Offset { + get { + document.DebugVerifyAccess(); + + TextAnchorNode n = this.node; + if (n == null) + throw new InvalidOperationException(); + + int offset = n.length; + if (n.left != null) + offset += n.left.totalLength; + while (n.parent != null) { + if (n == n.parent.right) { + if (n.parent.left != null) + offset += n.parent.left.totalLength; + offset += n.parent.length; + } + n = n.parent; + } + return offset; + } + } + + /// + /// Gets the line number of the anchor. + /// + /// Thrown when trying to get the Offset from a deleted anchor. + public int Line { + get { + return document.GetLineByOffset(this.Offset).LineNumber; + } + } + + /// + /// Gets the column number of this anchor. + /// + /// Thrown when trying to get the Offset from a deleted anchor. + public int Column { + get { + int offset = this.Offset; + return offset - document.GetLineByOffset(offset).Offset + 1; + } + } + + /// + /// Gets the text location of this anchor. + /// + /// Thrown when trying to get the Offset from a deleted anchor. + public TextLocation Location { + get { + return document.GetLocation(this.Offset); + } + } + + /// + public override string ToString() + { + return "[TextAnchor Offset=" + Offset + "]"; + } + } + + /// + /// Defines how a text anchor moves. + /// + public enum AnchorMovementType + { + /// + /// When text is inserted at the anchor position, the type of the insertion + /// determines where the caret moves to. For normal insertions, the anchor will stay + /// behind the inserted text. + /// + Default, + /// + /// When text is inserted at the anchor position, the anchor will stay + /// before the inserted text. + /// + BeforeInsertion, + /// + /// When text is insered at the anchor position, the anchor will move + /// after the inserted text. + /// + AfterInsertion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorNode.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorNode.cs new file mode 100644 index 000000000..bfa7c4eef --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorNode.cs @@ -0,0 +1,87 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// A TextAnchorNode is placed in the TextAnchorTree. + /// It describes a section of text with a text anchor at the end of the section. + /// A weak reference is used to refer to the TextAnchor. (to save memory, we derive from WeakReference instead of referencing it) + /// + sealed class TextAnchorNode : WeakReference + { + internal TextAnchorNode left, right, parent; + internal bool color; + internal int length; + internal int totalLength; // totalLength = length + left.totalLength + right.totalLength + + public TextAnchorNode(TextAnchor anchor) : base(anchor) + { + } + + internal TextAnchorNode LeftMost { + get { + TextAnchorNode node = this; + while (node.left != null) + node = node.left; + return node; + } + } + + internal TextAnchorNode RightMost { + get { + TextAnchorNode node = this; + while (node.right != null) + node = node.right; + return node; + } + } + + /// + /// Gets the inorder successor of the node. + /// + internal TextAnchorNode Successor { + get { + if (right != null) { + return right.LeftMost; + } else { + TextAnchorNode node = this; + TextAnchorNode oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a left subtree + } while (node != null && node.right == oldNode); + return node; + } + } + } + + /// + /// Gets the inorder predecessor of the node. + /// + internal TextAnchorNode Predecessor { + get { + if (left != null) { + return left.RightMost; + } else { + TextAnchorNode node = this; + TextAnchorNode oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a right subtree + } while (node != null && node.left == oldNode); + return node; + } + } + } + + public override string ToString() + { + return "[TextAnchorNode Length=" + length + " TotalLength=" + totalLength + " Target=" + Target + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorTree.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorTree.cs new file mode 100644 index 000000000..d07a33b9d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextAnchorTree.cs @@ -0,0 +1,753 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// A tree of TextAnchorNodes. + /// + sealed class TextAnchorTree + { + // The text anchor tree has difficult requirements: + // - it must QUICKLY update the offset in all anchors whenever there is a document change + // - it must not reference text anchors directly, using weak references instead + + // Clearly, we cannot afford updating an Offset property on all anchors (that would be O(N)). + // So instead, the anchors need to be able to calculate their offset from a data structure + // that can be efficiently updated. + + // This implementation is built using an augmented red-black-tree. + // There is a 'TextAnchorNode' for each text anchor. + // Such a node represents a section of text (just the length is stored) with a (weakly referenced) text anchor at the end. + + // Basically, you can imagine the list of text anchors as a sorted list of text anchors, where each anchor + // just stores the distance to the previous anchor. + // (next node = TextAnchorNode.Successor, distance = TextAnchorNode.length) + // Distances are never negative, so this representation means anchors are always sorted by offset + // (the order of anchors at the same offset is undefined) + + // Of course, a linked list of anchors would be way too slow (one would need to traverse the whole list + // every time the offset of an anchor is being looked up). + // Instead, we use a red-black-tree. We aren't actually using the tree for sorting - it's just a binary tree + // as storage format for what's conceptually a list, the red-black properties are used to keep the tree balanced. + // Other balanced binary trees would work, too. + + // What makes the tree-form efficient is that is augments the data by a 'totalLength'. Where 'length' + // represents the distance to the previous node, 'totalLength' is the sum of all 'length' values in the subtree + // under that node. + // This allows computing the Offset from an anchor by walking up the list of parent nodes instead of going + // through all predecessor nodes. So computing the Offset runs in O(log N). + + readonly TextDocument document; + readonly List nodesToDelete = new List(); + TextAnchorNode root; + + public TextAnchorTree(TextDocument document) + { + this.document = document; + } + + [Conditional("DEBUG")] + static void Log(string text) + { + Debug.WriteLine("TextAnchorTree: " + text); + } + + #region Insert Text + void InsertText(int offset, int length, bool defaultAnchorMovementIsBeforeInsertion) + { + if (length == 0 || root == null || offset > root.totalLength) + return; + + // find the range of nodes that are placed exactly at offset + // beginNode is inclusive, endNode is exclusive + if (offset == root.totalLength) { + PerformInsertText(FindActualBeginNode(root.RightMost), null, length, defaultAnchorMovementIsBeforeInsertion); + } else { + TextAnchorNode endNode = FindNode(ref offset); + Debug.Assert(endNode.length > 0); + + if (offset > 0) { + // there are no nodes exactly at offset + endNode.length += length; + UpdateAugmentedData(endNode); + } else { + PerformInsertText(FindActualBeginNode(endNode.Predecessor), endNode, length, defaultAnchorMovementIsBeforeInsertion); + } + } + DeleteMarkedNodes(); + } + + TextAnchorNode FindActualBeginNode(TextAnchorNode node) + { + // now find the actual beginNode + while (node != null && node.length == 0) + node = node.Predecessor; + if (node == null) { + // no predecessor = beginNode is first node in tree + node = root.LeftMost; + } + return node; + } + + // Sorts the nodes in the range [beginNode, endNode) by MovementType + // and inserts the length between the BeforeInsertion and the AfterInsertion nodes. + void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length, bool defaultAnchorMovementIsBeforeInsertion) + { + Debug.Assert(beginNode != null); + // endNode may be null at the end of the anchor tree + + // now we need to sort the nodes in the range [beginNode, endNode); putting those with + // MovementType.BeforeInsertion in front of those with MovementType.AfterInsertion + List beforeInsert = new List(); + //List afterInsert = new List(); + TextAnchorNode temp = beginNode; + while (temp != endNode) { + TextAnchor anchor = (TextAnchor)temp.Target; + if (anchor == null) { + // afterInsert.Add(temp); + MarkNodeForDelete(temp); + } else if (defaultAnchorMovementIsBeforeInsertion + ? anchor.MovementType != AnchorMovementType.AfterInsertion + : anchor.MovementType == AnchorMovementType.BeforeInsertion) + { + beforeInsert.Add(temp); +// } else { +// afterInsert.Add(temp); + } + temp = temp.Successor; + } + // now again go through the range and swap the nodes with those in the beforeInsert list + temp = beginNode; + foreach (TextAnchorNode node in beforeInsert) { + SwapAnchors(node, temp); + temp = temp.Successor; + } + // now temp is pointing to the first node that is afterInsert, + // or to endNode, if there is no afterInsert node at the offset + // So add the length to temp + if (temp == null) { + // temp might be null if endNode==null and no afterInserts + Debug.Assert(endNode == null); + } else { + temp.length += length; + UpdateAugmentedData(temp); + } + } + + /// + /// Swaps the anchors stored in the two nodes. + /// + void SwapAnchors(TextAnchorNode n1, TextAnchorNode n2) + { + if (n1 != n2) { + TextAnchor anchor1 = (TextAnchor)n1.Target; + TextAnchor anchor2 = (TextAnchor)n2.Target; + if (anchor1 == null && anchor2 == null) { + // -> no swap required + return; + } + n1.Target = anchor2; + n2.Target = anchor1; + if (anchor1 == null) { + // unmark n1 from deletion, mark n2 for deletion + nodesToDelete.Remove(n1); + MarkNodeForDelete(n2); + anchor2.node = n1; + } else if (anchor2 == null) { + // unmark n2 from deletion, mark n1 for deletion + nodesToDelete.Remove(n2); + MarkNodeForDelete(n1); + anchor1.node = n2; + } else { + anchor1.node = n2; + anchor2.node = n1; + } + } + } + #endregion + + #region Remove or Replace text + public void HandleTextChange(OffsetChangeMapEntry entry, DelayedEvents delayedEvents) + { + //Log("HandleTextChange(" + entry + ")"); + if (entry.RemovalLength == 0) { + // This is a pure insertion. + // Unlike a replace with removal, a pure insertion can result in nodes at the same location + // to split depending on their MovementType. + // Thus, we handle this case on a separate code path + // (the code below looks like it does something similar, but it can only split + // the set of deletion survivors, not all nodes at an offset) + InsertText(entry.Offset, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); + return; + } + // When handling a replacing text change, we need to: + // - find all anchors in the deleted segment and delete them / move them to the appropriate + // surviving side. + // - adjust the segment size between the left and right side + + int offset = entry.Offset; + int remainingRemovalLength = entry.RemovalLength; + // if the text change is happening after the last anchor, we don't have to do anything + if (root == null || offset >= root.totalLength) + return; + TextAnchorNode node = FindNode(ref offset); + TextAnchorNode firstDeletionSurvivor = null; + // go forward through the tree and delete all nodes in the removal segment + while (node != null && offset + remainingRemovalLength > node.length) { + TextAnchor anchor = (TextAnchor)node.Target; + if (anchor != null && (anchor.SurviveDeletion || entry.RemovalNeverCausesAnchorDeletion)) { + if (firstDeletionSurvivor == null) + firstDeletionSurvivor = node; + // This node should be deleted, but it wants to survive. + // We'll just remove the deleted length segment, so the node will be positioned + // in front of the removed segment. + remainingRemovalLength -= node.length - offset; + node.length = offset; + offset = 0; + UpdateAugmentedData(node); + node = node.Successor; + } else { + // delete node + TextAnchorNode s = node.Successor; + remainingRemovalLength -= node.length; + RemoveNode(node); + // we already deleted the node, don't delete it twice + nodesToDelete.Remove(node); + if (anchor != null) + anchor.OnDeleted(delayedEvents); + node = s; + } + } + // 'node' now is the first anchor after the deleted segment. + // If there are no anchors after the deleted segment, 'node' is null. + + // firstDeletionSurvivor was set to the first node surviving deletion. + // Because all non-surviving nodes up to 'node' were deleted, the node range + // [firstDeletionSurvivor, node) now refers to the set of all deletion survivors. + + // do the remaining job of the removal + if (node != null) { + node.length -= remainingRemovalLength; + Debug.Assert(node.length >= 0); + } + if (entry.InsertionLength > 0) { + // we are performing a replacement + if (firstDeletionSurvivor != null) { + // We got deletion survivors which need to be split into BeforeInsertion + // and AfterInsertion groups. + // Take care that we don't regroup everything at offset, but only the deletion + // survivors - from firstDeletionSurvivor (inclusive) to node (exclusive). + // This ensures that nodes immediately before or after the replaced segment + // stay where they are (independent from their MovementType) + PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion); + } else if (node != null) { + // No deletion survivors: + // just perform the insertion + node.length += entry.InsertionLength; + } + } + if (node != null) { + UpdateAugmentedData(node); + } + DeleteMarkedNodes(); + } + #endregion + + #region Node removal when TextAnchor was GC'ed + void MarkNodeForDelete(TextAnchorNode node) + { + if (!nodesToDelete.Contains(node)) + nodesToDelete.Add(node); + } + + void DeleteMarkedNodes() + { + CheckProperties(); + while (nodesToDelete.Count > 0) { + int pos = nodesToDelete.Count - 1; + TextAnchorNode n = nodesToDelete[pos]; + // combine section of n with the following section + TextAnchorNode s = n.Successor; + if (s != null) { + s.length += n.length; + } + RemoveNode(n); + if (s != null) { + UpdateAugmentedData(s); + } + nodesToDelete.RemoveAt(pos); + CheckProperties(); + } + CheckProperties(); + } + #endregion + + #region FindNode + /// + /// Finds the node at the specified offset. + /// After the method has run, offset is relative to the beginning of the returned node. + /// + TextAnchorNode FindNode(ref int offset) + { + TextAnchorNode n = root; + while (true) { + if (n.left != null) { + if (offset < n.left.totalLength) { + n = n.left; // descend into left subtree + continue; + } else { + offset -= n.left.totalLength; // skip left subtree + } + } + if (!n.IsAlive) + MarkNodeForDelete(n); + if (offset < n.length) { + return n; // found correct node + } else { + offset -= n.length; // skip this node + } + if (n.right != null) { + n = n.right; // descend into right subtree + } else { + // didn't find any node containing the offset + return null; + } + } + } + #endregion + + #region UpdateAugmentedData + void UpdateAugmentedData(TextAnchorNode n) + { + if (!n.IsAlive) + MarkNodeForDelete(n); + + int totalLength = n.length; + if (n.left != null) + totalLength += n.left.totalLength; + if (n.right != null) + totalLength += n.right.totalLength; + if (n.totalLength != totalLength) { + n.totalLength = totalLength; + if (n.parent != null) + UpdateAugmentedData(n.parent); + } + } + #endregion + + #region CreateAnchor + public TextAnchor CreateAnchor(int offset) + { + Log("CreateAnchor(" + offset + ")"); + TextAnchor anchor = new TextAnchor(document); + anchor.node = new TextAnchorNode(anchor); + if (root == null) { + // creating the first text anchor + root = anchor.node; + root.totalLength = root.length = offset; + } else if (offset >= root.totalLength) { + // append anchor at end of tree + anchor.node.totalLength = anchor.node.length = offset - root.totalLength; + InsertAsRight(root.RightMost, anchor.node); + } else { + // insert anchor in middle of tree + TextAnchorNode n = FindNode(ref offset); + Debug.Assert(offset < n.length); + // split segment 'n' at offset + anchor.node.totalLength = anchor.node.length = offset; + n.length -= offset; + InsertBefore(n, anchor.node); + } + DeleteMarkedNodes(); + return anchor; + } + + void InsertBefore(TextAnchorNode node, TextAnchorNode newNode) + { + if (node.left == null) { + InsertAsLeft(node, newNode); + } else { + InsertAsRight(node.left.RightMost, newNode); + } + } + #endregion + + #region Red/Black Tree + internal const bool RED = true; + internal const bool BLACK = false; + + void InsertAsLeft(TextAnchorNode parentNode, TextAnchorNode newNode) + { + Debug.Assert(parentNode.left == null); + parentNode.left = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAugmentedData(parentNode); + FixTreeOnInsert(newNode); + } + + void InsertAsRight(TextAnchorNode parentNode, TextAnchorNode newNode) + { + Debug.Assert(parentNode.right == null); + parentNode.right = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAugmentedData(parentNode); + FixTreeOnInsert(newNode); + } + + void FixTreeOnInsert(TextAnchorNode node) + { + Debug.Assert(node != null); + Debug.Assert(node.color == RED); + Debug.Assert(node.left == null || node.left.color == BLACK); + Debug.Assert(node.right == null || node.right.color == BLACK); + + TextAnchorNode parentNode = node.parent; + if (parentNode == null) { + // we inserted in the root -> the node must be black + // since this is a root node, making the node black increments the number of black nodes + // on all paths by one, so it is still the same for all paths. + node.color = BLACK; + return; + } + if (parentNode.color == BLACK) { + // if the parent node where we inserted was black, our red node is placed correctly. + // since we inserted a red node, the number of black nodes on each path is unchanged + // -> the tree is still balanced + return; + } + // parentNode is red, so there is a conflict here! + + // because the root is black, parentNode is not the root -> there is a grandparent node + TextAnchorNode grandparentNode = parentNode.parent; + TextAnchorNode uncleNode = Sibling(parentNode); + if (uncleNode != null && uncleNode.color == RED) { + parentNode.color = BLACK; + uncleNode.color = BLACK; + grandparentNode.color = RED; + FixTreeOnInsert(grandparentNode); + return; + } + // now we know: parent is red but uncle is black + // First rotation: + if (node == parentNode.right && parentNode == grandparentNode.left) { + RotateLeft(parentNode); + node = node.left; + } else if (node == parentNode.left && parentNode == grandparentNode.right) { + RotateRight(parentNode); + node = node.right; + } + // because node might have changed, reassign variables: + parentNode = node.parent; + grandparentNode = parentNode.parent; + + // Now recolor a bit: + parentNode.color = BLACK; + grandparentNode.color = RED; + // Second rotation: + if (node == parentNode.left && parentNode == grandparentNode.left) { + RotateRight(grandparentNode); + } else { + // because of the first rotation, this is guaranteed: + Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); + RotateLeft(grandparentNode); + } + } + + void RemoveNode(TextAnchorNode removedNode) + { + if (removedNode.left != null && removedNode.right != null) { + // replace removedNode with it's in-order successor + + TextAnchorNode leftMost = removedNode.right.LeftMost; + RemoveNode(leftMost); // remove leftMost from its current location + + // and overwrite the removedNode with it + ReplaceNode(removedNode, leftMost); + leftMost.left = removedNode.left; + if (leftMost.left != null) leftMost.left.parent = leftMost; + leftMost.right = removedNode.right; + if (leftMost.right != null) leftMost.right.parent = leftMost; + leftMost.color = removedNode.color; + + UpdateAugmentedData(leftMost); + if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent); + return; + } + + // now either removedNode.left or removedNode.right is null + // get the remaining child + TextAnchorNode parentNode = removedNode.parent; + TextAnchorNode childNode = removedNode.left ?? removedNode.right; + ReplaceNode(removedNode, childNode); + if (parentNode != null) UpdateAugmentedData(parentNode); + if (removedNode.color == BLACK) { + if (childNode != null && childNode.color == RED) { + childNode.color = BLACK; + } else { + FixTreeOnDelete(childNode, parentNode); + } + } + } + + void FixTreeOnDelete(TextAnchorNode node, TextAnchorNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (parentNode == null) + return; + + // warning: node may be null + TextAnchorNode sibling = Sibling(node, parentNode); + if (sibling.color == RED) { + parentNode.color = RED; + sibling.color = BLACK; + if (node == parentNode.left) { + RotateLeft(parentNode); + } else { + RotateRight(parentNode); + } + + sibling = Sibling(node, parentNode); // update value of sibling after rotation + } + + if (parentNode.color == BLACK + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + FixTreeOnDelete(parentNode, parentNode.parent); + return; + } + + if (parentNode.color == RED + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + parentNode.color = BLACK; + return; + } + + if (node == parentNode.left && + sibling.color == BLACK && + GetColor(sibling.left) == RED && + GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + sibling.left.color = BLACK; + RotateRight(sibling); + } + else if (node == parentNode.right && + sibling.color == BLACK && + GetColor(sibling.right) == RED && + GetColor(sibling.left) == BLACK) + { + sibling.color = RED; + sibling.right.color = BLACK; + RotateLeft(sibling); + } + sibling = Sibling(node, parentNode); // update value of sibling after rotation + + sibling.color = parentNode.color; + parentNode.color = BLACK; + if (node == parentNode.left) { + if (sibling.right != null) { + Debug.Assert(sibling.right.color == RED); + sibling.right.color = BLACK; + } + RotateLeft(parentNode); + } else { + if (sibling.left != null) { + Debug.Assert(sibling.left.color == RED); + sibling.left.color = BLACK; + } + RotateRight(parentNode); + } + } + + void ReplaceNode(TextAnchorNode replacedNode, TextAnchorNode newNode) + { + if (replacedNode.parent == null) { + Debug.Assert(replacedNode == root); + root = newNode; + } else { + if (replacedNode.parent.left == replacedNode) + replacedNode.parent.left = newNode; + else + replacedNode.parent.right = newNode; + } + if (newNode != null) { + newNode.parent = replacedNode.parent; + } + replacedNode.parent = null; + } + + void RotateLeft(TextAnchorNode p) + { + // let q be p's right child + TextAnchorNode q = p.right; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's right child to be q's left child + p.right = q.left; + if (p.right != null) p.right.parent = p; + // set q's left child to be p + q.left = p; + p.parent = q; + UpdateAugmentedData(p); + UpdateAugmentedData(q); + } + + void RotateRight(TextAnchorNode p) + { + // let q be p's left child + TextAnchorNode q = p.left; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's left child to be q's right child + p.left = q.right; + if (p.left != null) p.left.parent = p; + // set q's right child to be p + q.right = p; + p.parent = q; + UpdateAugmentedData(p); + UpdateAugmentedData(q); + } + + static TextAnchorNode Sibling(TextAnchorNode node) + { + if (node == node.parent.left) + return node.parent.right; + else + return node.parent.left; + } + + static TextAnchorNode Sibling(TextAnchorNode node, TextAnchorNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (node == parentNode.left) + return parentNode.right; + else + return parentNode.left; + } + + static bool GetColor(TextAnchorNode node) + { + return node != null ? node.color : BLACK; + } + #endregion + + #region CheckProperties + [Conditional("DATACONSISTENCYTEST")] + internal void CheckProperties() + { + #if DEBUG + if (root != null) { + CheckProperties(root); + + // check red-black property: + int blackCount = -1; + CheckNodeProperties(root, null, RED, 0, ref blackCount); + } + #endif + } + + #if DEBUG + void CheckProperties(TextAnchorNode node) + { + int totalLength = node.length; + if (node.left != null) { + CheckProperties(node.left); + totalLength += node.left.totalLength; + } + if (node.right != null) { + CheckProperties(node.right); + totalLength += node.right.totalLength; + } + Debug.Assert(node.totalLength == totalLength); + } + + /* + 1. A node is either red or black. + 2. The root is black. + 3. All leaves are black. (The leaves are the NIL children.) + 4. Both children of every red node are black. (So every red node must have a black parent.) + 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) + */ + void CheckNodeProperties(TextAnchorNode node, TextAnchorNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) + { + if (node == null) return; + + Debug.Assert(node.parent == parentNode); + + if (parentColor == RED) { + Debug.Assert(node.color == BLACK); + } + if (node.color == BLACK) { + blackCount++; + } + if (node.left == null && node.right == null) { + // node is a leaf node: + if (expectedBlackCount == -1) + expectedBlackCount = blackCount; + else + Debug.Assert(expectedBlackCount == blackCount); + } + CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); + CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); + } + #endif + #endregion + + #region GetTreeAsString + #if DEBUG + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public string GetTreeAsString() + { + if (root == null) + return ""; + StringBuilder b = new StringBuilder(); + AppendTreeToString(root, b, 0); + return b.ToString(); + } + + static void AppendTreeToString(TextAnchorNode node, StringBuilder b, int indent) + { + if (node.color == RED) + b.Append("RED "); + else + b.Append("BLACK "); + b.AppendLine(node.ToString()); + indent += 2; + if (node.left != null) { + b.Append(' ', indent); + b.Append("L: "); + AppendTreeToString(node.left, b, indent); + } + if (node.right != null) { + b.Append(' ', indent); + b.Append("R: "); + AppendTreeToString(node.right, b, indent); + } + } + #endif + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs new file mode 100644 index 000000000..84fc86f44 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs @@ -0,0 +1,836 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Globalization; +using System.Linq.Expressions; +using System.Threading; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// This class is the main class of the text model. Basically, it is a with events. + /// + /// + /// Thread safety: + /// + /// However, there is a single method that is thread-safe: (and its overloads). + /// + public sealed class TextDocument : ITextSource, INotifyPropertyChanged + { + #region Thread ownership + readonly object lockObject = new object(); + Thread owner = Thread.CurrentThread; + + /// + /// Verifies that the current thread is the documents owner thread. + /// Throws an if the wrong thread accesses the TextDocument. + /// + /// + /// The TextDocument class is not thread-safe. A document instance expects to have a single owner thread + /// and will throw an when accessed from another thread. + /// It is possible to change the owner thread using the method. + /// + public void VerifyAccess() + { + if (Thread.CurrentThread != owner) + throw new InvalidOperationException("TextDocument can be accessed only from the thread that owns it."); + } + + /// + /// Transfers ownership of the document to another thread. This method can be used to load + /// a file into a TextDocument on a background thread and then transfer ownership to the UI thread + /// for displaying the document. + /// + /// + /// + /// + /// The owner can be set to null, which means that no thread can access the document. But, if the document + /// has no owner thread, any thread may take ownership by calling . + /// + /// + public void SetOwnerThread(Thread newOwner) + { + // We need to lock here to ensure that in the null owner case, + // only one thread succeeds in taking ownership. + lock (lockObject) { + if (owner != null) { + VerifyAccess(); + } + owner = newOwner; + } + } + #endregion + + #region Fields + Constructor + readonly Rope rope; + readonly DocumentLineTree lineTree; + readonly LineManager lineManager; + readonly TextAnchorTree anchorTree; + ChangeTrackingCheckpoint currentCheckpoint; + + /// + /// Create an empty text document. + /// + public TextDocument() + : this(string.Empty) + { + } + + /// + /// Create a new text document with the specified initial text. + /// + public TextDocument(IEnumerable initialText) + { + if (initialText == null) + throw new ArgumentNullException("initialText"); + rope = new Rope(initialText); + lineTree = new DocumentLineTree(this); + lineManager = new LineManager(lineTree, this); + lineTrackers.CollectionChanged += delegate { + lineManager.UpdateListOfLineTrackers(); + }; + + anchorTree = new TextAnchorTree(this); + undoStack = new UndoStack(); + FireChangeEvents(); + } + + /// + /// Create a new text document with the specified initial text. + /// + public TextDocument(ITextSource initialText) + : this(GetTextFromTextSource(initialText)) + { + } + + // gets the text from a text source, directly retrieving the underlying rope where possible + static IEnumerable GetTextFromTextSource(ITextSource textSource) + { + if (textSource == null) + throw new ArgumentNullException("textSource"); + + RopeTextSource rts = textSource as RopeTextSource; + if (rts != null) + return rts.GetRope(); + + TextDocument doc = textSource as TextDocument; + if (doc != null) + return doc.rope; + + return textSource.Text; + } + #endregion + + #region Text + void ThrowIfRangeInvalid(int offset, int length) + { + if (offset < 0 || offset > rope.Length) { + throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString(CultureInfo.InvariantCulture)); + } + if (length < 0 || offset + length > rope.Length) { + throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + rope.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + /// + public string GetText(int offset, int length) + { + VerifyAccess(); + return rope.ToString(offset, length); + } + + /// + /// Retrieves the text for a portion of the document. + /// + public string GetText(ISegment segment) + { + if (segment == null) + throw new ArgumentNullException("segment"); + return GetText(segment.Offset, segment.Length); + } + + int ITextSource.IndexOfAny(char[] anyOf, int startIndex, int count) + { + DebugVerifyAccess(); // frequently called (NewLineFinder), so must be fast in release builds + return rope.IndexOfAny(anyOf, startIndex, count); + } + + /// + public char GetCharAt(int offset) + { + DebugVerifyAccess(); // frequently called, so must be fast in release builds + return rope[offset]; + } + + WeakReference cachedText; + + /// + /// Gets/Sets the text of the whole document. + /// + public string Text { + get { + VerifyAccess(); + string completeText = cachedText != null ? (cachedText.Target as string) : null; + if (completeText == null) { + completeText = rope.ToString(); + cachedText = new WeakReference(completeText); + } + return completeText; + } + set { + VerifyAccess(); + if (value == null) + throw new ArgumentNullException("value"); + Replace(0, rope.Length, value); + } + } + + /// + /// + public event EventHandler TextChanged; + + /// + public int TextLength { + get { + VerifyAccess(); + return rope.Length; + } + } + + /// + /// Is raised when the TextLength property changes. + /// + /// + [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")] + public event EventHandler TextLengthChanged; + + /// + /// Is raised when one of the properties , , , + /// changes. + /// + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Is raised before the document changes. + /// + /// + /// Here is the order in which events are raised during a document update: + /// + /// BeginUpdate() + /// + /// Start of change group (on undo stack) + /// event is raised + /// + /// Insert() / Remove() / Replace() + /// + /// event is raised + /// The document is changed + /// TextAnchor.Deleted event is raised if anchors were + /// in the deleted text portion + /// event is raised + /// + /// EndUpdate() + /// + /// event is raised + /// event is raised (for the Text, TextLength, LineCount properties, in that order) + /// End of change group (on undo stack) + /// event is raised + /// + /// + /// + /// If the insert/remove/replace methods are called without a call to BeginUpdate(), + /// they will call BeginUpdate() and EndUpdate() to ensure no change happens outside of UpdateStarted/UpdateFinished. + /// + /// There can be multiple document changes between the BeginUpdate() and EndUpdate() calls. + /// In this case, the events associated with EndUpdate will be raised only once after the whole document update is done. + /// + /// The listens to the UpdateStarted and UpdateFinished events to group all changes into a single undo step. + /// + /// + public event EventHandler Changing; + + /// + /// Is raised after the document has changed. + /// + /// + public event EventHandler Changed; + + /// + /// Creates a snapshot of the current text. + /// + /// + /// This method returns an immutable snapshot of the document, and may be safely called even when + /// the document's owner thread is concurrently modifying the document. + /// + /// This special thread-safety guarantee is valid only for TextDocument.CreateSnapshot(), not necessarily for other + /// classes implementing ITextSource.CreateSnapshot(). + /// + /// + /// + public ITextSource CreateSnapshot() + { + lock (lockObject) { + return new RopeTextSource(rope.Clone()); + } + } + + /// + /// Creates a snapshot of the current text. + /// Additionally, creates a checkpoint that allows tracking document changes. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Need to return snapshot and checkpoint together to ensure thread-safety")] + public ITextSource CreateSnapshot(out ChangeTrackingCheckpoint checkpoint) + { + lock (lockObject) { + if (currentCheckpoint == null) + currentCheckpoint = new ChangeTrackingCheckpoint(lockObject); + checkpoint = currentCheckpoint; + return new RopeTextSource(rope.Clone()); + } + } + + internal ChangeTrackingCheckpoint CreateChangeTrackingCheckpoint() + { + lock (lockObject) { + if (currentCheckpoint == null) + currentCheckpoint = new ChangeTrackingCheckpoint(lockObject); + return currentCheckpoint; + } + } + + /// + /// Creates a snapshot of a part of the current text. + /// + /// + public ITextSource CreateSnapshot(int offset, int length) + { + lock (lockObject) { + return new RopeTextSource(rope.GetRange(offset, length)); + } + } + + /// + public System.IO.TextReader CreateReader() + { + lock (lockObject) { + return new RopeTextReader(rope); + } + } + #endregion + + #region BeginUpdate / EndUpdate + int beginUpdateCount; + + /// + /// Gets if an update is running. + /// + /// + public bool IsInUpdate { + get { + VerifyAccess(); + return beginUpdateCount > 0; + } + } + + /// + /// Immediately calls , + /// and returns an IDisposable that calls . + /// + /// + public IDisposable RunUpdate() + { + BeginUpdate(); + return new CallbackOnDispose(EndUpdate); + } + + /// + /// Begins a group of document changes. + /// Some events are suspended until EndUpdate is called, and the will + /// group all changes into a single action. + /// Calling BeginUpdate several times increments a counter, only after the appropriate number + /// of EndUpdate calls the events resume their work. + /// + /// + public void BeginUpdate() + { + VerifyAccess(); + if (inDocumentChanging) + throw new InvalidOperationException("Cannot change document within another document change."); + beginUpdateCount++; + if (beginUpdateCount == 1) { + undoStack.StartUndoGroup(); + if (UpdateStarted != null) + UpdateStarted(this, EventArgs.Empty); + } + } + + /// + /// Ends a group of document changes. + /// + /// + public void EndUpdate() + { + VerifyAccess(); + if (inDocumentChanging) + throw new InvalidOperationException("Cannot end update within document change."); + if (beginUpdateCount == 0) + throw new InvalidOperationException("No update is active."); + if (beginUpdateCount == 1) { + // fire change events inside the change group - event handlers might add additional + // document changes to the change group + FireChangeEvents(); + undoStack.EndUndoGroup(); + beginUpdateCount = 0; + if (UpdateFinished != null) + UpdateFinished(this, EventArgs.Empty); + } else { + beginUpdateCount -= 1; + } + } + + /// + /// Occurs when a document change starts. + /// + /// + public event EventHandler UpdateStarted; + + /// + /// Occurs when a document change is finished. + /// + /// + public event EventHandler UpdateFinished; + #endregion + + #region Fire events after update + int oldTextLength; + int oldLineCount; + bool fireTextChanged; + + /// + /// Fires TextChanged, TextLengthChanged, LineCountChanged if required. + /// + internal void FireChangeEvents() + { + // it may be necessary to fire the event multiple times if the document is changed + // from inside the event handlers + while (fireTextChanged) { + fireTextChanged = false; + if (TextChanged != null) + TextChanged(this, EventArgs.Empty); + OnPropertyChanged("Text"); + + int textLength = rope.Length; + if (textLength != oldTextLength) { + oldTextLength = textLength; + if (TextLengthChanged != null) + TextLengthChanged(this, EventArgs.Empty); + OnPropertyChanged("TextLength"); + } + int lineCount = lineTree.LineCount; + if (lineCount != oldLineCount) { + oldLineCount = lineCount; + if (LineCountChanged != null) + LineCountChanged(this, EventArgs.Empty); + OnPropertyChanged("LineCount"); + } + } + } + + void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + + #region Insert / Remove / Replace + /// + /// Inserts text. + /// + public void Insert(int offset, string text) + { + Replace(offset, 0, text); + } + + /// + /// Removes text. + /// + public void Remove(ISegment segment) + { + Replace(segment, string.Empty); + } + + /// + /// Removes text. + /// + public void Remove(int offset, int length) + { + Replace(offset, length, string.Empty); + } + + internal bool inDocumentChanging; + + /// + /// Replaces text. + /// + public void Replace(ISegment segment, string text) + { + if (segment == null) + throw new ArgumentNullException("segment"); + Replace(segment.Offset, segment.Length, text, null); + } + + /// + /// Replaces text. + /// + public void Replace(int offset, int length, string text) + { + Replace(offset, length, text, null); + } + + /// + /// Replaces text. + /// + /// The starting offset of the text to be replaced. + /// The length of the text to be replaced. + /// The new text. + /// The offsetChangeMappingType determines how offsets inside the old text are mapped to the new text. + /// This affects how the anchors and segments inside the replaced region behave. + public void Replace(int offset, int length, string text, OffsetChangeMappingType offsetChangeMappingType) + { + if (text == null) + throw new ArgumentNullException("text"); + // Please see OffsetChangeMappingType XML comments for details on how these modes work. + switch (offsetChangeMappingType) { + case OffsetChangeMappingType.Normal: + Replace(offset, length, text, null); + break; + case OffsetChangeMappingType.KeepAnchorBeforeInsertion: + Replace(offset, length, text, OffsetChangeMap.FromSingleElement( + new OffsetChangeMapEntry(offset, length, text.Length, false, true))); + break; + case OffsetChangeMappingType.RemoveAndInsert: + if (length == 0 || text.Length == 0) { + // only insertion or only removal? + // OffsetChangeMappingType doesn't matter, just use Normal. + Replace(offset, length, text, null); + } else { + OffsetChangeMap map = new OffsetChangeMap(2); + map.Add(new OffsetChangeMapEntry(offset, length, 0)); + map.Add(new OffsetChangeMapEntry(offset, 0, text.Length)); + map.Freeze(); + Replace(offset, length, text, map); + } + break; + case OffsetChangeMappingType.CharacterReplace: + if (length == 0 || text.Length == 0) { + // only insertion or only removal? + // OffsetChangeMappingType doesn't matter, just use Normal. + Replace(offset, length, text, null); + } else if (text.Length > length) { + // look at OffsetChangeMappingType.CharacterReplace XML comments on why we need to replace + // the last character + OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + length - 1, 1, 1 + text.Length - length); + Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry)); + } else if (text.Length < length) { + OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + text.Length, length - text.Length, 0, true, false); + Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry)); + } else { + Replace(offset, length, text, OffsetChangeMap.Empty); + } + break; + default: + throw new ArgumentOutOfRangeException("offsetChangeMappingType", offsetChangeMappingType, "Invalid enum value"); + } + } + + /// + /// Replaces text. + /// + /// The starting offset of the text to be replaced. + /// The length of the text to be replaced. + /// The new text. + /// The offsetChangeMap determines how offsets inside the old text are mapped to the new text. + /// This affects how the anchors and segments inside the replaced region behave. + /// If you pass null (the default when using one of the other overloads), the offsets are changed as + /// in OffsetChangeMappingType.Normal mode. + /// If you pass OffsetChangeMap.Empty, then everything will stay in its old place (OffsetChangeMappingType.CharacterReplace mode). + /// The offsetChangeMap must be a valid 'explanation' for the document change. See . + /// Passing an OffsetChangeMap to the Replace method will automatically freeze it to ensure the thread safety of the resulting + /// DocumentChangeEventArgs instance. + /// + public void Replace(int offset, int length, string text, OffsetChangeMap offsetChangeMap) + { + if (text == null) + throw new ArgumentNullException("text"); + + if (offsetChangeMap != null) + offsetChangeMap.Freeze(); + + // Ensure that all changes take place inside an update group. + // Will also take care of throwing an exception if inDocumentChanging is set. + BeginUpdate(); + try { + // protect document change against corruption by other changes inside the event handlers + inDocumentChanging = true; + try { + // The range verification must wait until after the BeginUpdate() call because the document + // might be modified inside the UpdateStarted event. + ThrowIfRangeInvalid(offset, length); + + DoReplace(offset, length, text, offsetChangeMap); + } finally { + inDocumentChanging = false; + } + } finally { + EndUpdate(); + } + } + + void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap) + { + if (length == 0 && newText.Length == 0) + return; + + // trying to replace a single character in 'Normal' mode? + // for single characters, 'CharacterReplace' mode is equivalent, but more performant + // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode) + if (length == 1 && newText.Length == 1 && offsetChangeMap == null) + offsetChangeMap = OffsetChangeMap.Empty; + + string removedText = rope.ToString(offset, length); + DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap); + + // fire DocumentChanging event + if (Changing != null) + Changing(this, args); + + undoStack.Push(this, args); + + cachedText = null; // reset cache of complete document text + fireTextChanged = true; + DelayedEvents delayedEvents = new DelayedEvents(); + + lock (lockObject) { + // create linked list of checkpoints, if required + if (currentCheckpoint != null) { + currentCheckpoint = currentCheckpoint.Append(args); + } + + // now update the textBuffer and lineTree + if (offset == 0 && length == rope.Length) { + // optimize replacing the whole document + rope.Clear(); + rope.InsertText(0, newText); + lineManager.Rebuild(); + } else { + rope.RemoveRange(offset, length); + lineManager.Remove(offset, length); + #if DEBUG + lineTree.CheckProperties(); + #endif + rope.InsertText(offset, newText); + lineManager.Insert(offset, newText); + #if DEBUG + lineTree.CheckProperties(); + #endif + } + } + + // update text anchors + if (offsetChangeMap == null) { + anchorTree.HandleTextChange(args.CreateSingleChangeMapEntry(), delayedEvents); + } else { + foreach (OffsetChangeMapEntry entry in offsetChangeMap) { + anchorTree.HandleTextChange(entry, delayedEvents); + } + } + + // raise delayed events after our data structures are consistent again + delayedEvents.RaiseEvents(); + + // fire DocumentChanged event + if (Changed != null) + Changed(this, args); + } + #endregion + + #region GetLineBy... + /// + /// Gets a read-only list of lines. + /// + /// + public IList Lines { + get { return lineTree; } + } + + /// + /// Gets a line by the line number: O(log n) + /// + public DocumentLine GetLineByNumber(int number) + { + VerifyAccess(); + if (number < 1 || number > lineTree.LineCount) + throw new ArgumentOutOfRangeException("number", number, "Value must be between 1 and " + lineTree.LineCount); + return lineTree.GetByNumber(number); + } + + /// + /// Gets a document lines by offset. + /// Runtime: O(log n) + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] + public DocumentLine GetLineByOffset(int offset) + { + VerifyAccess(); + if (offset < 0 || offset > rope.Length) { + throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString()); + } + return lineTree.GetByOffset(offset); + } + #endregion + + /// + /// Gets the offset from a text location. + /// + /// + public int GetOffset(TextLocation location) + { + return GetOffset(location.Line, location.Column); + } + + /// + /// Gets the offset from a text location. + /// + /// + public int GetOffset(int line, int column) + { + DocumentLine docLine = GetLineByNumber(line); + if (column <= 0) + return docLine.Offset; + if (column > docLine.Length) + return docLine.EndOffset; + return docLine.Offset + column - 1; + } + + /// + /// Gets the location from an offset. + /// + /// + public TextLocation GetLocation(int offset) + { + DocumentLine line = GetLineByOffset(offset); + return new TextLocation(line.LineNumber, offset - line.Offset + 1); + } + + readonly ObservableCollection lineTrackers = new ObservableCollection(); + + /// + /// Gets the list of s attached to this document. + /// You can add custom line trackers to this list. + /// + public IList LineTrackers { + get { + VerifyAccess(); + return lineTrackers; + } + } + + UndoStack undoStack; + + /// + /// Gets the of the document. + /// + /// This property can also be used to set the undo stack, e.g. for sharing a common undo stack between multiple documents. + public UndoStack UndoStack { + get { return undoStack; } + set { + if (value == null) + throw new ArgumentNullException(); + if (value != undoStack) { + undoStack.ClearAll(); // first clear old undo stack, so that it can't be used to perform unexpected changes on this document + // ClearAll() will also throw an exception when it's not safe to replace the undo stack (e.g. update is currently in progress) + undoStack = value; + OnPropertyChanged("UndoStack"); + } + } + } + + /// + /// Creates a new at the specified offset. + /// + /// + public TextAnchor CreateAnchor(int offset) + { + VerifyAccess(); + if (offset < 0 || offset > rope.Length) { + throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString(CultureInfo.InvariantCulture)); + } + return anchorTree.CreateAnchor(offset); + } + + #region LineCount + /// + /// Gets the total number of lines in the document. + /// Runtime: O(1). + /// + public int LineCount { + get { + VerifyAccess(); + return lineTree.LineCount; + } + } + + /// + /// Is raised when the LineCount property changes. + /// + [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")] + public event EventHandler LineCountChanged; + #endregion + + #region Debugging + [Conditional("DEBUG")] + internal void DebugVerifyAccess() + { + VerifyAccess(); + } + + /// + /// Gets the document lines tree in string form. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + internal string GetLineTreeAsString() + { + #if DEBUG + return lineTree.GetTreeAsString(); + #else + return "Not available in release build."; + #endif + } + + /// + /// Gets the text anchor tree in string form. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + internal string GetTextAnchorTreeAsString() + { + #if DEBUG + return anchorTree.GetTreeAsString(); + #else + return "Not available in release build."; + #endif + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocumentWeakEventManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocumentWeakEventManager.cs new file mode 100644 index 000000000..de304b722 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocumentWeakEventManager.cs @@ -0,0 +1,149 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Contains weak event managers for the TextDocument events. + /// + public static class TextDocumentWeakEventManager + { + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class UpdateStarted : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.UpdateStarted += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.UpdateStarted -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class UpdateFinished : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.UpdateFinished += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.UpdateFinished -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class Changing : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.Changing += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.Changing -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class Changed : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.Changed += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.Changed -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + [Obsolete("The TextDocument.LineCountChanged event will be removed in a future version. Use PropertyChangedEventManager instead.")] + public sealed class LineCountChanged : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.LineCountChanged += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.LineCountChanged -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + [Obsolete("The TextDocument.TextLengthChanged event will be removed in a future version. Use PropertyChangedEventManager instead.")] + public sealed class TextLengthChanged : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.TextLengthChanged += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.TextLengthChanged -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class TextChanged : WeakEventManagerBase + { + /// + protected override void StartListening(TextDocument source) + { + source.TextChanged += DeliverEvent; + } + + /// + protected override void StopListening(TextDocument source) + { + source.TextChanged -= DeliverEvent; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextLocation.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextLocation.cs new file mode 100644 index 000000000..1d3c2564c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextLocation.cs @@ -0,0 +1,166 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// A line/column position. + /// Text editor lines/columns are counted started from one. + /// + /// + /// The document provides the methods and + /// to convert between offsets and TextLocations. + /// + public struct TextLocation : IComparable, IEquatable + { + /// + /// Represents no text location (0, 0). + /// + public static readonly TextLocation Empty = new TextLocation(0, 0); + + /// + /// Creates a TextLocation instance. + /// + /// Warning: the parameters are (line, column). + /// Not (column, line) as in ICSharpCode.TextEditor! + /// + /// + public TextLocation(int line, int column) + { + y = line; + x = column; + } + + int x, y; + + /// + /// Gets the line number. + /// + public int Line { + get { return y; } + } + + /// + /// Gets the column number. + /// + public int Column { + get { return x; } + } + + /// + /// Gets whether the TextLocation instance is empty. + /// + public bool IsEmpty { + get { + return x <= 0 && y <= 0; + } + } + + /// + /// Gets a string representation for debugging purposes. + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "(Line {1}, Col {0})", this.x, this.y); + } + + /// + /// Gets a hash code. + /// + public override int GetHashCode() + { + return unchecked (87 * x.GetHashCode() ^ y.GetHashCode()); + } + + /// + /// Equality test. + /// + public override bool Equals(object obj) + { + if (!(obj is TextLocation)) return false; + return (TextLocation)obj == this; + } + + /// + /// Equality test. + /// + public bool Equals(TextLocation other) + { + return this == other; + } + + /// + /// Equality test. + /// + public static bool operator ==(TextLocation left, TextLocation right) + { + return left.x == right.x && left.y == right.y; + } + + /// + /// Inequality test. + /// + public static bool operator !=(TextLocation left, TextLocation right) + { + return left.x != right.x || left.y != right.y; + } + + /// + /// Compares two text locations. + /// + public static bool operator <(TextLocation left, TextLocation right) + { + if (left.y < right.y) + return true; + else if (left.y == right.y) + return left.x < right.x; + else + return false; + } + + /// + /// Compares two text locations. + /// + public static bool operator >(TextLocation left, TextLocation right) + { + if (left.y > right.y) + return true; + else if (left.y == right.y) + return left.x > right.x; + else + return false; + } + + /// + /// Compares two text locations. + /// + public static bool operator <=(TextLocation left, TextLocation right) + { + return !(left > right); + } + + /// + /// Compares two text locations. + /// + public static bool operator >=(TextLocation left, TextLocation right) + { + return !(left < right); + } + + /// + /// Compares two text locations. + /// + public int CompareTo(TextLocation other) + { + if (this == other) + return 0; + if (this < other) + return -1; + else + return 1; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegment.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegment.cs new file mode 100644 index 000000000..d1834c214 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegment.cs @@ -0,0 +1,248 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// A segment that can be put into a . + /// + /// + /// + /// A can be stand-alone or part of a . + /// If the segment is stored inside a TextSegmentCollection, its Offset and Length will be updated by that collection. + /// + /// + /// When the document changes, the offsets of all text segments in the TextSegmentCollection will be adjusted accordingly. + /// Start offsets move like AnchorMovementType.AfterInsertion, + /// end offsets move like AnchorMovementType.BeforeInsertion + /// (i.e. the segment will always stay as small as possible). + /// + /// If a document change causes a segment to be deleted completely, it will be reduced to length 0, but segments are + /// never automatically removed from the collection. + /// Segments with length 0 will never expand due to document changes, and they move as AfterInsertion. + /// + /// + /// Thread-safety: a TextSegmentCollection that is connected to a may only be used on that document's owner thread. + /// A disconnected TextSegmentCollection is safe for concurrent reads, but concurrent access is not safe when there are writes. + /// Keep in mind that reading the Offset properties of a text segment inside the collection is a read access on the + /// collection; and setting an Offset property of a text segment is a write access on the collection. + /// + /// + /// + /// + /// + public class TextSegment : ISegment + { + internal ISegmentTree ownerTree; + internal TextSegment left, right, parent; + + /// + /// The color of the segment in the red/black tree. + /// + internal bool color; + + /// + /// The "length" of the node (distance to previous node) + /// + internal int nodeLength; + + /// + /// The total "length" of this subtree. + /// + internal int totalNodeLength; // totalNodeLength = nodeLength + left.totalNodeLength + right.totalNodeLength + + /// + /// The length of the segment (do not confuse with nodeLength). + /// + internal int segmentLength; + + /// + /// distanceToMaxEnd = Max(segmentLength, + /// left.distanceToMaxEnd + left.Offset - Offset, + /// left.distanceToMaxEnd + right.Offset - Offset) + /// + internal int distanceToMaxEnd; + + int ISegment.Offset { + get { return StartOffset; } + } + + /// + /// Gets whether this segment is connected to a TextSegmentCollection and will automatically + /// update its offsets. + /// + protected bool IsConnectedToCollection { + get { + return ownerTree != null; + } + } + + /// + /// Gets/Sets the start offset of the segment. + /// + /// + /// When setting the start offset, the end offset will change, too: the Length of the segment will stay constant. + /// + public int StartOffset { + get { + // If the segment is not connected to a tree, we store the offset in "nodeLength". + // Otherwise, "nodeLength" contains the distance to the start offset of the previous node + Debug.Assert(!(ownerTree == null && parent != null)); + Debug.Assert(!(ownerTree == null && left != null)); + + TextSegment n = this; + int offset = n.nodeLength; + if (n.left != null) + offset += n.left.totalNodeLength; + while (n.parent != null) { + if (n == n.parent.right) { + if (n.parent.left != null) + offset += n.parent.left.totalNodeLength; + offset += n.parent.nodeLength; + } + n = n.parent; + } + return offset; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException("value", "Offset must not be negative"); + if (this.StartOffset != value) { + // need a copy of the variable because ownerTree.Remove() sets this.ownerTree to null + ISegmentTree ownerTree = this.ownerTree; + if (ownerTree != null) { + ownerTree.Remove(this); + nodeLength = value; + ownerTree.Add(this); + } else { + nodeLength = value; + } + OnSegmentChanged(); + } + } + } + + /// + /// Gets/Sets the end offset of the segment. + /// + /// + /// Setting the end offset will change the length, the start offset will stay constant. + /// + public int EndOffset { + get { + return StartOffset + Length; + } + set { + int newLength = value - StartOffset; + if (newLength < 0) + throw new ArgumentOutOfRangeException("value", "EndOffset must be greater or equal to StartOffset"); + Length = newLength; + } + } + + /// + /// Gets/Sets the length of the segment. + /// + /// + /// Setting the length will change the end offset, the start offset will stay constant. + /// + public int Length { + get { + return segmentLength; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException("value", "Length must not be negative"); + if (segmentLength != value) { + segmentLength = value; + if (ownerTree != null) + ownerTree.UpdateAugmentedData(this); + OnSegmentChanged(); + } + } + } + + /// + /// This method gets called when the StartOffset/Length/EndOffset properties are set. + /// It is not called when StartOffset/Length/EndOffset change due to document changes + /// + protected virtual void OnSegmentChanged() + { + } + + internal TextSegment LeftMost { + get { + TextSegment node = this; + while (node.left != null) + node = node.left; + return node; + } + } + + internal TextSegment RightMost { + get { + TextSegment node = this; + while (node.right != null) + node = node.right; + return node; + } + } + + /// + /// Gets the inorder successor of the node. + /// + internal TextSegment Successor { + get { + if (right != null) { + return right.LeftMost; + } else { + TextSegment node = this; + TextSegment oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a left subtree + } while (node != null && node.right == oldNode); + return node; + } + } + } + + /// + /// Gets the inorder predecessor of the node. + /// + internal TextSegment Predecessor { + get { + if (left != null) { + return left.RightMost; + } else { + TextSegment node = this; + TextSegment oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a right subtree + } while (node != null && node.left == oldNode); + return node; + } + } + } + + #if DEBUG + internal string ToDebugString() + { + return "[nodeLength=" + nodeLength + " totalNodeLength=" + totalNodeLength + + " distanceToMaxEnd=" + distanceToMaxEnd + " MaxEndOffset=" + (StartOffset + distanceToMaxEnd) + "]"; + } + #endif + + /// + public override string ToString() + { + return "[" + GetType().Name + " Offset=" + StartOffset + " Length=" + Length + " EndOffset=" + EndOffset + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs new file mode 100644 index 000000000..549dbb77d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextSegmentCollection.cs @@ -0,0 +1,971 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Windows; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Interface to allow TextSegments to access the TextSegmentCollection - we cannot use a direct reference + /// because TextSegmentCollection is generic. + /// + interface ISegmentTree + { + void Add(TextSegment s); + void Remove(TextSegment s); + void UpdateAugmentedData(TextSegment s); + } + + /// + /// + /// A collection of text segments that supports efficient lookup of segments + /// intersecting with another segment. + /// + /// + /// + /// + public sealed class TextSegmentCollection : ICollection, ISegmentTree, IWeakEventListener where T : TextSegment + { + // Implementation: this is basically a mixture of an augmented interval tree + // and the TextAnchorTree. + + // WARNING: you need to understand interval trees (the version with the augmented 'high'/'max' field) + // and how the TextAnchorTree works before you have any chance of understanding this code. + + // This means that every node holds two "segments": + // one like the segments in the text anchor tree to support efficient offset changes + // and another that is the interval as seen by the user + + // So basically, the tree contains a list of contiguous node segments of the first kind, + // with interval segments starting at the end of every node segment. + + // Performance: + // Add is O(lg n) + // Remove is O(lg n) + // DocumentChanged is O(m * lg n), with m the number of segments that intersect with the changed document section + // FindFirstSegmentWithStartAfter is O(m + lg n) with m being the number of segments at the same offset as the result segment + // FindIntersectingSegments is O(m + lg n) with m being the number of intersecting segments. + + int count; + TextSegment root; + bool isConnectedToDocument; + + #region Constructor + /// + /// Creates a new TextSegmentCollection that needs manual calls to . + /// + public TextSegmentCollection() + { + } + + /// + /// Creates a new TextSegmentCollection that updates the offsets automatically. + /// + /// The document to which the text segments + /// that will be added to the tree belong. When the document changes, the + /// position of the text segments will be updated accordingly. + public TextSegmentCollection(TextDocument textDocument) + { + if (textDocument == null) + throw new ArgumentNullException("textDocument"); + + textDocument.VerifyAccess(); + isConnectedToDocument = true; + TextDocumentWeakEventManager.Changed.AddListener(textDocument, this); + } + #endregion + + #region OnDocumentChanged / UpdateOffsets + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextDocumentWeakEventManager.Changed)) { + OnDocumentChanged((DocumentChangeEventArgs)e); + return true; + } + return false; + } + + /// + /// Updates the start and end offsets of all segments stored in this collection. + /// + /// DocumentChangeEventArgs instance describing the change to the document. + public void UpdateOffsets(DocumentChangeEventArgs e) + { + if (e == null) + throw new ArgumentNullException("e"); + if (isConnectedToDocument) + throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!"); + OnDocumentChanged(e); + CheckProperties(); + } + + void OnDocumentChanged(DocumentChangeEventArgs e) + { + OffsetChangeMap map = e.OffsetChangeMapOrNull; + if (map != null) { + foreach (OffsetChangeMapEntry entry in map) { + UpdateOffsetsInternal(entry); + } + } else { + UpdateOffsetsInternal(e.CreateSingleChangeMapEntry()); + } + } + + /// + /// Updates the start and end offsets of all segments stored in this collection. + /// + /// OffsetChangeMapEntry instance describing the change to the document. + public void UpdateOffsets(OffsetChangeMapEntry change) + { + if (isConnectedToDocument) + throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!"); + UpdateOffsetsInternal(change); + CheckProperties(); + } + #endregion + + #region UpdateOffsets (implementation) + void UpdateOffsetsInternal(OffsetChangeMapEntry change) + { + // Special case pure insertions, because they don't always cause a text segment to increase in size when the replaced region + // is inside a segment (when offset is at start or end of a text semgent). + if (change.RemovalLength == 0) { + InsertText(change.Offset, change.InsertionLength); + } else { + ReplaceText(change); + } + } + + void InsertText(int offset, int length) + { + if (length == 0) + return; + + // enlarge segments that contain offset (excluding those that have offset as endpoint) + foreach (TextSegment segment in FindSegmentsContaining(offset)) { + if (segment.StartOffset < offset && offset < segment.EndOffset) { + segment.Length += length; + } + } + + // move start offsets of all segments >= offset + TextSegment node = FindFirstSegmentWithStartAfter(offset); + if (node != null) { + node.nodeLength += length; + UpdateAugmentedData(node); + } + } + + void ReplaceText(OffsetChangeMapEntry change) + { + Debug.Assert(change.RemovalLength > 0); + int offset = change.Offset; + foreach (TextSegment segment in FindOverlappingSegments(offset, change.RemovalLength)) { + if (segment.StartOffset <= offset) { + if (segment.EndOffset >= offset + change.RemovalLength) { + // Replacement inside segment: adjust segment length + segment.Length += change.InsertionLength - change.RemovalLength; + } else { + // Replacement starting inside segment and ending after segment end: set segment end to removal position + //segment.EndOffset = offset; + segment.Length = offset - segment.StartOffset; + } + } else { + // Replacement starting in front of text segment and running into segment. + // Keep segment.EndOffset constant and move segment.StartOffset to the end of the replacement + int remainingLength = segment.EndOffset - (offset + change.RemovalLength); + RemoveSegment(segment); + segment.StartOffset = offset + change.RemovalLength; + segment.Length = Math.Max(0, remainingLength); + AddSegment(segment); + } + } + // move start offsets of all segments > offset + TextSegment node = FindFirstSegmentWithStartAfter(offset + 1); + if (node != null) { + Debug.Assert(node.nodeLength >= change.RemovalLength); + node.nodeLength += change.InsertionLength - change.RemovalLength; + UpdateAugmentedData(node); + } + } + #endregion + + #region Add + /// + /// Adds the specified segment to the tree. This will cause the segment to update when the + /// document changes. + /// + public void Add(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + if (item.ownerTree != null) + throw new ArgumentException("The segment is already added to a SegmentCollection."); + AddSegment(item); + } + + void ISegmentTree.Add(TextSegment s) + { + AddSegment(s); + } + + void AddSegment(TextSegment node) + { + int insertionOffset = node.StartOffset; + node.distanceToMaxEnd = node.segmentLength; + if (root == null) { + root = node; + node.totalNodeLength = node.nodeLength; + } else if (insertionOffset >= root.totalNodeLength) { + // append segment at end of tree + node.nodeLength = node.totalNodeLength = insertionOffset - root.totalNodeLength; + InsertAsRight(root.RightMost, node); + } else { + // insert in middle of tree + TextSegment n = FindNode(ref insertionOffset); + Debug.Assert(insertionOffset < n.nodeLength); + // split node segment 'n' at offset + node.totalNodeLength = node.nodeLength = insertionOffset; + n.nodeLength -= insertionOffset; + InsertBefore(n, node); + } + node.ownerTree = this; + count++; + CheckProperties(); + } + + void InsertBefore(TextSegment node, TextSegment newNode) + { + if (node.left == null) { + InsertAsLeft(node, newNode); + } else { + InsertAsRight(node.left.RightMost, newNode); + } + } + #endregion + + #region GetNextSegment / GetPreviousSegment + /// + /// Gets the next segment after the specified segment. + /// Segments are sorted by their start offset. + /// Returns null if segment is the last segment. + /// + public T GetNextSegment(T segment) + { + if (!Contains(segment)) + throw new ArgumentException("segment is not inside the segment tree"); + return (T)segment.Successor; + } + + /// + /// Gets the previous segment before the specified segment. + /// Segments are sorted by their start offset. + /// Returns null if segment is the first segment. + /// + public T GetPreviousSegment(T segment) + { + if (!Contains(segment)) + throw new ArgumentException("segment is not inside the segment tree"); + return (T)segment.Predecessor; + } + #endregion + + #region FirstSegment/LastSegment + /// + /// Returns the first segment in the collection or null, if the collection is empty. + /// + public T FirstSegment { + get { + return root == null ? null : (T)root.LeftMost; + } + } + + /// + /// Returns the last segment in the collection or null, if the collection is empty. + /// + public T LastSegment { + get { + return root == null ? null : (T)root.RightMost; + } + } + #endregion + + #region FindFirstSegmentWithStartAfter + /// + /// Gets the first segment with a start offset greater or equal to . + /// Returns null if no such segment is found. + /// + public T FindFirstSegmentWithStartAfter(int startOffset) + { + if (root == null) + return null; + if (startOffset <= 0) + return (T)root.LeftMost; + TextSegment s = FindNode(ref startOffset); + // startOffset means that the previous segment is starting at the offset we were looking for + while (startOffset == 0) { + TextSegment p = (s == null) ? root.RightMost : s.Predecessor; + // There must always be a predecessor: if we were looking for the first node, we would have already + // returned it as root.LeftMost above. + Debug.Assert(p != null); + startOffset += p.nodeLength; + s = p; + } + return (T)s; + } + + /// + /// Finds the node at the specified offset. + /// After the method has run, offset is relative to the beginning of the returned node. + /// + TextSegment FindNode(ref int offset) + { + TextSegment n = root; + while (true) { + if (n.left != null) { + if (offset < n.left.totalNodeLength) { + n = n.left; // descend into left subtree + continue; + } else { + offset -= n.left.totalNodeLength; // skip left subtree + } + } + if (offset < n.nodeLength) { + return n; // found correct node + } else { + offset -= n.nodeLength; // skip this node + } + if (n.right != null) { + n = n.right; // descend into right subtree + } else { + // didn't find any node containing the offset + return null; + } + } + } + #endregion + + #region FindOverlappingSegments + /// + /// Finds all segments that contain the given offset. + /// (StartOffset <= offset <= EndOffset) + /// Segments are returned in the order given by GetNextSegment/GetPreviousSegment. + /// + /// Returns a new collection containing the results of the query. + /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection. + public ReadOnlyCollection FindSegmentsContaining(int offset) + { + return FindOverlappingSegments(offset, 0); + } + + /// + /// Finds all segments that overlap with the given segment (including touching segments). + /// + /// Returns a new collection containing the results of the query. + /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection. + public ReadOnlyCollection FindOverlappingSegments(ISegment segment) + { + if (segment == null) + throw new ArgumentNullException("segment"); + return FindOverlappingSegments(segment.Offset, segment.Length); + } + + /// + /// Finds all segments that overlap with the given segment (including touching segments). + /// Segments are returned in the order given by GetNextSegment/GetPreviousSegment. + /// + /// Returns a new collection containing the results of the query. + /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection. + public ReadOnlyCollection FindOverlappingSegments(int offset, int length) + { + ThrowUtil.CheckNotNegative(length, "length"); + List results = new List(); + if (root != null) { + FindOverlappingSegments(results, root, offset, offset + length); + } + return results.AsReadOnly(); + } + + void FindOverlappingSegments(List results, TextSegment node, int low, int high) + { + // low and high are relative to node.LeftMost startpos (not node.LeftMost.Offset) + if (high < 0) { + // node is irrelevant for search because all intervals in node are after high + return; + } + + // find values relative to node.Offset + int nodeLow = low - node.nodeLength; + int nodeHigh = high - node.nodeLength; + if (node.left != null) { + nodeLow -= node.left.totalNodeLength; + nodeHigh -= node.left.totalNodeLength; + } + + if (node.distanceToMaxEnd < nodeLow) { + // node is irrelevant for search because all intervals in node are before low + return; + } + + if (node.left != null) + FindOverlappingSegments(results, node.left, low, high); + + if (nodeHigh < 0) { + // node and everything in node.right is before low + return; + } + + if (nodeLow <= node.segmentLength) { + results.Add((T)node); + } + + if (node.right != null) + FindOverlappingSegments(results, node.right, nodeLow, nodeHigh); + } + #endregion + + #region UpdateAugmentedData + void UpdateAugmentedData(TextSegment node) + { + int totalLength = node.nodeLength; + int distanceToMaxEnd = node.segmentLength; + if (node.left != null) { + totalLength += node.left.totalNodeLength; + + int leftDTME = node.left.distanceToMaxEnd; + // dtme is relative, so convert it to the coordinates of node: + if (node.left.right != null) + leftDTME -= node.left.right.totalNodeLength; + leftDTME -= node.nodeLength; + if (leftDTME > distanceToMaxEnd) + distanceToMaxEnd = leftDTME; + } + if (node.right != null) { + totalLength += node.right.totalNodeLength; + + int rightDTME = node.right.distanceToMaxEnd; + // dtme is relative, so convert it to the coordinates of node: + rightDTME += node.right.nodeLength; + if (node.right.left != null) + rightDTME += node.right.left.totalNodeLength; + if (rightDTME > distanceToMaxEnd) + distanceToMaxEnd = rightDTME; + } + if (node.totalNodeLength != totalLength + || node.distanceToMaxEnd != distanceToMaxEnd) + { + node.totalNodeLength = totalLength; + node.distanceToMaxEnd = distanceToMaxEnd; + if (node.parent != null) + UpdateAugmentedData(node.parent); + } + } + + void ISegmentTree.UpdateAugmentedData(TextSegment node) + { + UpdateAugmentedData(node); + } + #endregion + + #region Remove + /// + /// Removes the specified segment from the tree. This will cause the segment to not update + /// anymore when the document changes. + /// + public bool Remove(T item) + { + if (!Contains(item)) + return false; + RemoveSegment(item); + return true; + } + + void ISegmentTree.Remove(TextSegment s) + { + RemoveSegment(s); + } + + void RemoveSegment(TextSegment s) + { + int oldOffset = s.StartOffset; + TextSegment successor = s.Successor; + if (successor != null) + successor.nodeLength += s.nodeLength; + RemoveNode(s); + if (successor != null) + UpdateAugmentedData(successor); + Disconnect(s, oldOffset); + CheckProperties(); + } + + void Disconnect(TextSegment s, int offset) + { + s.left = s.right = s.parent = null; + s.ownerTree = null; + s.nodeLength = offset; + count--; + } + + /// + /// Removes all segments from the tree. + /// + public void Clear() + { + T[] segments = this.ToArray(); + root = null; + int offset = 0; + foreach (TextSegment s in segments) { + offset += s.nodeLength; + Disconnect(s, offset); + } + CheckProperties(); + } + #endregion + + #region CheckProperties + [Conditional("DATACONSISTENCYTEST")] + internal void CheckProperties() + { + #if DEBUG + if (root != null) { + CheckProperties(root); + + // check red-black property: + int blackCount = -1; + CheckNodeProperties(root, null, RED, 0, ref blackCount); + } + + int expectedCount = 0; + // we cannot trust LINQ not to call ICollection.Count, so we need this loop + // to count the elements in the tree + using (IEnumerator en = GetEnumerator()) { + while (en.MoveNext()) expectedCount++; + } + Debug.Assert(count == expectedCount); + #endif + } + + #if DEBUG + void CheckProperties(TextSegment node) + { + int totalLength = node.nodeLength; + int distanceToMaxEnd = node.segmentLength; + if (node.left != null) { + CheckProperties(node.left); + totalLength += node.left.totalNodeLength; + distanceToMaxEnd = Math.Max(distanceToMaxEnd, + node.left.distanceToMaxEnd + node.left.StartOffset - node.StartOffset); + } + if (node.right != null) { + CheckProperties(node.right); + totalLength += node.right.totalNodeLength; + distanceToMaxEnd = Math.Max(distanceToMaxEnd, + node.right.distanceToMaxEnd + node.right.StartOffset - node.StartOffset); + } + Debug.Assert(node.totalNodeLength == totalLength); + Debug.Assert(node.distanceToMaxEnd == distanceToMaxEnd); + } + + /* + 1. A node is either red or black. + 2. The root is black. + 3. All leaves are black. (The leaves are the NIL children.) + 4. Both children of every red node are black. (So every red node must have a black parent.) + 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) + */ + void CheckNodeProperties(TextSegment node, TextSegment parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) + { + if (node == null) return; + + Debug.Assert(node.parent == parentNode); + + if (parentColor == RED) { + Debug.Assert(node.color == BLACK); + } + if (node.color == BLACK) { + blackCount++; + } + if (node.left == null && node.right == null) { + // node is a leaf node: + if (expectedBlackCount == -1) + expectedBlackCount = blackCount; + else + Debug.Assert(expectedBlackCount == blackCount); + } + CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); + CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); + } + + static void AppendTreeToString(TextSegment node, StringBuilder b, int indent) + { + if (node.color == RED) + b.Append("RED "); + else + b.Append("BLACK "); + b.AppendLine(node.ToString() + node.ToDebugString()); + indent += 2; + if (node.left != null) { + b.Append(' ', indent); + b.Append("L: "); + AppendTreeToString(node.left, b, indent); + } + if (node.right != null) { + b.Append(' ', indent); + b.Append("R: "); + AppendTreeToString(node.right, b, indent); + } + } + #endif + + internal string GetTreeAsString() + { + #if DEBUG + StringBuilder b = new StringBuilder(); + if (root != null) + AppendTreeToString(root, b, 0); + return b.ToString(); + #else + return "Not available in release build."; + #endif + } + #endregion + + #region Red/Black Tree + internal const bool RED = true; + internal const bool BLACK = false; + + void InsertAsLeft(TextSegment parentNode, TextSegment newNode) + { + Debug.Assert(parentNode.left == null); + parentNode.left = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAugmentedData(parentNode); + FixTreeOnInsert(newNode); + } + + void InsertAsRight(TextSegment parentNode, TextSegment newNode) + { + Debug.Assert(parentNode.right == null); + parentNode.right = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAugmentedData(parentNode); + FixTreeOnInsert(newNode); + } + + void FixTreeOnInsert(TextSegment node) + { + Debug.Assert(node != null); + Debug.Assert(node.color == RED); + Debug.Assert(node.left == null || node.left.color == BLACK); + Debug.Assert(node.right == null || node.right.color == BLACK); + + TextSegment parentNode = node.parent; + if (parentNode == null) { + // we inserted in the root -> the node must be black + // since this is a root node, making the node black increments the number of black nodes + // on all paths by one, so it is still the same for all paths. + node.color = BLACK; + return; + } + if (parentNode.color == BLACK) { + // if the parent node where we inserted was black, our red node is placed correctly. + // since we inserted a red node, the number of black nodes on each path is unchanged + // -> the tree is still balanced + return; + } + // parentNode is red, so there is a conflict here! + + // because the root is black, parentNode is not the root -> there is a grandparent node + TextSegment grandparentNode = parentNode.parent; + TextSegment uncleNode = Sibling(parentNode); + if (uncleNode != null && uncleNode.color == RED) { + parentNode.color = BLACK; + uncleNode.color = BLACK; + grandparentNode.color = RED; + FixTreeOnInsert(grandparentNode); + return; + } + // now we know: parent is red but uncle is black + // First rotation: + if (node == parentNode.right && parentNode == grandparentNode.left) { + RotateLeft(parentNode); + node = node.left; + } else if (node == parentNode.left && parentNode == grandparentNode.right) { + RotateRight(parentNode); + node = node.right; + } + // because node might have changed, reassign variables: + parentNode = node.parent; + grandparentNode = parentNode.parent; + + // Now recolor a bit: + parentNode.color = BLACK; + grandparentNode.color = RED; + // Second rotation: + if (node == parentNode.left && parentNode == grandparentNode.left) { + RotateRight(grandparentNode); + } else { + // because of the first rotation, this is guaranteed: + Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); + RotateLeft(grandparentNode); + } + } + + void RemoveNode(TextSegment removedNode) + { + if (removedNode.left != null && removedNode.right != null) { + // replace removedNode with it's in-order successor + + TextSegment leftMost = removedNode.right.LeftMost; + RemoveNode(leftMost); // remove leftMost from its current location + + // and overwrite the removedNode with it + ReplaceNode(removedNode, leftMost); + leftMost.left = removedNode.left; + if (leftMost.left != null) leftMost.left.parent = leftMost; + leftMost.right = removedNode.right; + if (leftMost.right != null) leftMost.right.parent = leftMost; + leftMost.color = removedNode.color; + + UpdateAugmentedData(leftMost); + if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent); + return; + } + + // now either removedNode.left or removedNode.right is null + // get the remaining child + TextSegment parentNode = removedNode.parent; + TextSegment childNode = removedNode.left ?? removedNode.right; + ReplaceNode(removedNode, childNode); + if (parentNode != null) UpdateAugmentedData(parentNode); + if (removedNode.color == BLACK) { + if (childNode != null && childNode.color == RED) { + childNode.color = BLACK; + } else { + FixTreeOnDelete(childNode, parentNode); + } + } + } + + void FixTreeOnDelete(TextSegment node, TextSegment parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (parentNode == null) + return; + + // warning: node may be null + TextSegment sibling = Sibling(node, parentNode); + if (sibling.color == RED) { + parentNode.color = RED; + sibling.color = BLACK; + if (node == parentNode.left) { + RotateLeft(parentNode); + } else { + RotateRight(parentNode); + } + + sibling = Sibling(node, parentNode); // update value of sibling after rotation + } + + if (parentNode.color == BLACK + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + FixTreeOnDelete(parentNode, parentNode.parent); + return; + } + + if (parentNode.color == RED + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + parentNode.color = BLACK; + return; + } + + if (node == parentNode.left && + sibling.color == BLACK && + GetColor(sibling.left) == RED && + GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + sibling.left.color = BLACK; + RotateRight(sibling); + } + else if (node == parentNode.right && + sibling.color == BLACK && + GetColor(sibling.right) == RED && + GetColor(sibling.left) == BLACK) + { + sibling.color = RED; + sibling.right.color = BLACK; + RotateLeft(sibling); + } + sibling = Sibling(node, parentNode); // update value of sibling after rotation + + sibling.color = parentNode.color; + parentNode.color = BLACK; + if (node == parentNode.left) { + if (sibling.right != null) { + Debug.Assert(sibling.right.color == RED); + sibling.right.color = BLACK; + } + RotateLeft(parentNode); + } else { + if (sibling.left != null) { + Debug.Assert(sibling.left.color == RED); + sibling.left.color = BLACK; + } + RotateRight(parentNode); + } + } + + void ReplaceNode(TextSegment replacedNode, TextSegment newNode) + { + if (replacedNode.parent == null) { + Debug.Assert(replacedNode == root); + root = newNode; + } else { + if (replacedNode.parent.left == replacedNode) + replacedNode.parent.left = newNode; + else + replacedNode.parent.right = newNode; + } + if (newNode != null) { + newNode.parent = replacedNode.parent; + } + replacedNode.parent = null; + } + + void RotateLeft(TextSegment p) + { + // let q be p's right child + TextSegment q = p.right; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's right child to be q's left child + p.right = q.left; + if (p.right != null) p.right.parent = p; + // set q's left child to be p + q.left = p; + p.parent = q; + UpdateAugmentedData(p); + UpdateAugmentedData(q); + } + + void RotateRight(TextSegment p) + { + // let q be p's left child + TextSegment q = p.left; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's left child to be q's right child + p.left = q.right; + if (p.left != null) p.left.parent = p; + // set q's right child to be p + q.right = p; + p.parent = q; + UpdateAugmentedData(p); + UpdateAugmentedData(q); + } + + static TextSegment Sibling(TextSegment node) + { + if (node == node.parent.left) + return node.parent.right; + else + return node.parent.left; + } + + static TextSegment Sibling(TextSegment node, TextSegment parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (node == parentNode.left) + return parentNode.right; + else + return parentNode.left; + } + + static bool GetColor(TextSegment node) + { + return node != null ? node.color : BLACK; + } + #endregion + + #region ICollection implementation + /// + /// Gets the number of segments in the tree. + /// + public int Count { + get { return count; } + } + + bool ICollection.IsReadOnly { + get { return false; } + } + + /// + /// Gets whether this tree contains the specified item. + /// + public bool Contains(T item) + { + return item != null && item.ownerTree == this; + } + + /// + /// Copies all segments in this SegmentTree to the specified array. + /// + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (array.Length < this.Count) + throw new ArgumentException("The array is too small", "array"); + if (arrayIndex < 0 || arrayIndex + count > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - count)); + foreach (T s in this) { + array[arrayIndex++] = s; + } + } + + /// + /// Gets an enumerator to enumerate the segments. + /// + public IEnumerator GetEnumerator() + { + if (root != null) { + TextSegment current = root.LeftMost; + while (current != null) { + yield return (T)current; + // TODO: check if collection was modified during enumeration + current = current.Successor; + } + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextUtilities.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextUtilities.cs new file mode 100644 index 000000000..a0428c4e3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextUtilities.cs @@ -0,0 +1,332 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using System.Windows.Documents; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Specifies the mode for getting the next caret position. + /// + public enum CaretPositioningMode + { + /// + /// Normal positioning (stop at every caret position) + /// + Normal, + /// + /// Stop only on word borders. + /// + WordBorder, + /// + /// Stop only at the beginning of words. This is used for Ctrl+Left/Ctrl+Right. + /// + WordStart, + /// + /// Stop only at the beginning of words, and anywhere in the middle of symbols. + /// + WordStartOrSymbol, + /// + /// Stop only on word borders, and anywhere in the middle of symbols. + /// + WordBorderOrSymbol + } + + /// + /// Static helper methods for working with text. + /// + public static partial class TextUtilities + { + #region GetControlCharacterName + // the names of the first 32 ASCII characters = Unicode C0 block + static readonly string[] c0Table = { + "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", + "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", + "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", + "RS", "US" + }; + + // DEL (ASCII 127) and + // the names of the control characters in the C1 block (Unicode 128 to 159) + static readonly string[] delAndC1Table = { + "DEL", + "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", "HTS", "HTJ", + "VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS", + "CCH", "MW", "SPA", "EPA", "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", + "PM", "APC" + }; + + /// + /// Gets the name of the control character. + /// For unknown characters, the unicode codepoint is returned as 4-digit hexadecimal value. + /// + public static string GetControlCharacterName(char controlCharacter) + { + int num = (int)controlCharacter; + if (num < c0Table.Length) + return c0Table[num]; + else if (num >= 127 && num <= 159) + return delAndC1Table[num - 127]; + else + return num.ToString("x4", CultureInfo.InvariantCulture); + } + #endregion + + #region GetWhitespace + /// + /// Gets all whitespace (' ' and '\t', but no newlines) after offset. + /// + /// The text source. + /// The offset where the whitespace starts. + /// The segment containing the whitespace. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", + Justification = "WPF uses 'Whitespace'")] + public static ISegment GetWhitespaceAfter(ITextSource textSource, int offset) + { + if (textSource == null) + throw new ArgumentNullException("textSource"); + int pos; + for (pos = offset; pos < textSource.TextLength; pos++) { + char c = textSource.GetCharAt(pos); + if (c != ' ' && c != '\t') + break; + } + return new SimpleSegment(offset, pos - offset); + } + + /// + /// Gets all whitespace (' ' and '\t', but no newlines) before offset. + /// + /// The text source. + /// The offset where the whitespace ends. + /// The segment containing the whitespace. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", + Justification = "WPF uses 'Whitespace'")] + public static ISegment GetWhitespaceBefore(ITextSource textSource, int offset) + { + if (textSource == null) + throw new ArgumentNullException("textSource"); + int pos; + for (pos = offset - 1; pos >= 0; pos--) { + char c = textSource.GetCharAt(pos); + if (c != ' ' && c != '\t') + break; + } + pos++; // go back the one character that isn't whitespace + return new SimpleSegment(pos, offset - pos); + } + + /// + /// Gets the leading whitespace segment on the document line. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", + Justification = "WPF uses 'Whitespace'")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Parameter cannot be ITextSource because it must belong to the DocumentLine")] + public static ISegment GetLeadingWhitespace(TextDocument document, DocumentLine documentLine) + { + if (documentLine == null) + throw new ArgumentNullException("documentLine"); + return GetWhitespaceAfter(document, documentLine.Offset); + } + + /// + /// Gets the trailing whitespace segment on the document line. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", + Justification = "WPF uses 'Whitespace'")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Parameter cannot be ITextSource because it must belong to the DocumentLine")] + public static ISegment GetTrailingWhitespace(TextDocument document, DocumentLine documentLine) + { + if (documentLine == null) + throw new ArgumentNullException("documentLine"); + ISegment segment = GetWhitespaceBefore(document, documentLine.EndOffset); + // If the whole line consists of whitespace, we consider all of it as leading whitespace, + // so return an empty segment as trailing whitespace. + if (segment.Offset == documentLine.Offset) + return new SimpleSegment(documentLine.EndOffset, 0); + else + return segment; + } + #endregion + + #region GetSingleIndentationSegment + /// + /// Gets a single indentation segment starting at - at most one tab + /// or spaces. + /// + /// The text source. + /// The offset where the indentation segment starts. + /// The size of an indentation unit. See . + /// The indentation segment. + /// If there is no indentation character at the specified , + /// an empty segment is returned. + public static ISegment GetSingleIndentationSegment(ITextSource textSource, int offset, int indentationSize) + { + if (textSource == null) + throw new ArgumentNullException("textSource"); + int pos = offset; + while (pos < textSource.TextLength) { + char c = textSource.GetCharAt(pos); + if (c == '\t') { + if (pos == offset) + return new SimpleSegment(offset, 1); + else + break; + } else if (c == ' ') { + if (pos - offset >= indentationSize) + break; + } else { + break; + } + // continue only if c==' ' and (pos-offset) + /// Gets whether the character is whitespace, part of an identifier, or line terminator. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")] + public static CharacterClass GetCharacterClass(char c) + { + if (c == '\r' || c == '\n') + return CharacterClass.LineTerminator; + else if (char.IsWhiteSpace(c)) + return CharacterClass.Whitespace; + else if (char.IsLetterOrDigit(c) || c == '_') + return CharacterClass.IdentifierPart; + else + return CharacterClass.Other; + } + #endregion + + #region GetNextCaretPosition + /// + /// Gets the next caret position. + /// + /// The text source. + /// The start offset inside the text source. + /// The search direction (forwards or backwards). + /// The mode for caret positioning. + /// The offset of the next caret position, or -1 if there is no further caret position + /// in the text source. + /// + /// This method is NOT equivalent to the actual caret movement when using VisualLine.GetNextCaretPosition. + /// In real caret movement, there are additional caret stops at line starts and ends. This method + /// treats linefeeds as simple whitespace. + /// + public static int GetNextCaretPosition(ITextSource textSource, int offset, LogicalDirection direction, CaretPositioningMode mode) + { + if (textSource == null) + throw new ArgumentNullException("textSource"); + if (mode != CaretPositioningMode.Normal + && mode != CaretPositioningMode.WordBorder + && mode != CaretPositioningMode.WordStart + && mode != CaretPositioningMode.WordBorderOrSymbol + && mode != CaretPositioningMode.WordStartOrSymbol) + { + throw new ArgumentException("Unsupported CaretPositioningMode: " + mode, "mode"); + } + if (direction != LogicalDirection.Backward + && direction != LogicalDirection.Forward) + { + throw new ArgumentException("Invalid LogicalDirection: " + direction, "direction"); + } + int textLength = textSource.TextLength; + if (textLength <= 0) { + // empty document? has a normal caret position at 0, though no word borders + if (mode == CaretPositioningMode.Normal) { + if (offset > 0 && direction == LogicalDirection.Backward) return 0; + if (offset < 0 && direction == LogicalDirection.Forward) return 0; + } + return -1; + } + while (true) { + int nextPos = (direction == LogicalDirection.Backward) ? offset - 1 : offset + 1; + + // return -1 if there is no further caret position in the text source + // we also need this to handle offset values outside the valid range + if (nextPos < 0 || nextPos > textLength) + return -1; + + // stop at every caret position? we can stop immediately. + if (mode == CaretPositioningMode.Normal) + return nextPos; + // not normal mode? we're looking for word borders... + + // check if we've run against the textSource borders. + // a 'textSource' usually isn't the whole document, but a single VisualLineElement. + if (nextPos == 0) { + // at the document start, there's only a word border + // if the first character is not whitespace + if (!char.IsWhiteSpace(textSource.GetCharAt(0))) + return nextPos; + } else if (nextPos == textLength) { + // at the document end, there's never a word start + if (mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) { + // at the document end, there's only a word border + // if the last character is not whitespace + if (!char.IsWhiteSpace(textSource.GetCharAt(textLength - 1))) + return nextPos; + } + } else { + CharacterClass charBefore = GetCharacterClass(textSource.GetCharAt(nextPos - 1)); + CharacterClass charAfter = GetCharacterClass(textSource.GetCharAt(nextPos)); + if (charBefore == charAfter) { + if (charBefore == CharacterClass.Other && + (mode == CaretPositioningMode.WordBorderOrSymbol || mode == CaretPositioningMode.WordStartOrSymbol)) + { + // With the "OrSymbol" modes, there's a word border and start between any two unknown characters + return nextPos; + } + } else { + // this looks like a possible border + + // if we're looking for word starts, check that this is a word start (and not a word end) + // if we're just checking for word borders, accept unconditionally + if (!((mode == CaretPositioningMode.WordStart || mode == CaretPositioningMode.WordStartOrSymbol) + && (charAfter == CharacterClass.Whitespace || charAfter == CharacterClass.LineTerminator))) + { + return nextPos; + } + } + } + // we'll have to continue searching... + offset = nextPos; + } + } + #endregion + } + + /// + /// Classifies a character as whitespace, line terminator, part of an identifier, or other. + /// + public enum CharacterClass + { + /// + /// The character is not whitespace, line terminator or part of an identifier. + /// + Other, + /// + /// The character is whitespace (but not line terminator). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", + Justification = "WPF uses 'Whitespace'")] + Whitespace, + /// + /// The character can be part of an identifier (Letter, digit or underscore). + /// + IdentifierPart, + /// + /// The character is line terminator (\r or \n). + /// + LineTerminator + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoOperationGroup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoOperationGroup.cs new file mode 100644 index 000000000..12afe3838 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoOperationGroup.cs @@ -0,0 +1,61 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// This class stacks the last x operations from the undostack and makes + /// one undo/redo operation from it. + /// + sealed class UndoOperationGroup : IUndoableOperationWithContext + { + IUndoableOperation[] undolist; + + public UndoOperationGroup(Deque stack, int numops) + { + if (stack == null) { + throw new ArgumentNullException("stack"); + } + + Debug.Assert(numops > 0 , "UndoOperationGroup : numops should be > 0"); + Debug.Assert(numops <= stack.Count); + + undolist = new IUndoableOperation[numops]; + for (int i = 0; i < numops; ++i) { + undolist[i] = stack.PopBack(); + } + } + + public void Undo() + { + for (int i = 0; i < undolist.Length; ++i) { + undolist[i].Undo(); + } + } + + public void Undo(UndoStack stack) + { + for (int i = 0; i < undolist.Length; ++i) { + stack.RunUndo(undolist[i]); + } + } + + public void Redo() + { + for (int i = undolist.Length - 1; i >= 0; --i) { + undolist[i].Redo(); + } + } + + public void Redo(UndoStack stack) + { + for (int i = undolist.Length - 1; i >= 0; --i) { + stack.RunRedo(undolist[i]); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs new file mode 100644 index 000000000..f0a759b23 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs @@ -0,0 +1,450 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Undo stack implementation. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public sealed class UndoStack : INotifyPropertyChanged + { + /// undo stack is listening for changes + internal const int StateListen = 0; + /// undo stack is reverting/repeating a set of changes + internal const int StatePlayback = 1; + // undo stack is reverting/repeating a set of changes and modifies the document to do this + internal const int StatePlaybackModifyDocument = 2; + /// state is used for checking that noone but the UndoStack performs changes + /// during Undo events + internal int state = StateListen; + + Deque undostack = new Deque(); + Deque redostack = new Deque(); + int sizeLimit = int.MaxValue; + + int undoGroupDepth; + int actionCountInUndoGroup; + int optionalActionCount; + object lastGroupDescriptor; + bool allowContinue; + + #region IsOriginalFile implementation + // implements feature request SD2-784 - File still considered dirty after undoing all changes + + /// + /// Number of times undo must be executed until the original state is reached. + /// Negative: number of times redo must be executed until the original state is reached. + /// Special case: int.MinValue == original state is unreachable + /// + int elementsOnUndoUntilOriginalFile; + + bool isOriginalFile = true; + + /// + /// Gets whether the document is currently in its original state (no modifications). + /// + public bool IsOriginalFile { + get { return isOriginalFile; } + } + + void RecalcIsOriginalFile() + { + bool newIsOriginalFile = (elementsOnUndoUntilOriginalFile == 0); + if (newIsOriginalFile != isOriginalFile) { + isOriginalFile = newIsOriginalFile; + NotifyPropertyChanged("IsOriginalFile"); + } + } + + /// + /// Marks the current state as original. Discards any previous "original" markers. + /// + public void MarkAsOriginalFile() + { + elementsOnUndoUntilOriginalFile = 0; + RecalcIsOriginalFile(); + } + + /// + /// Discards the current "original" marker. + /// + public void DiscardOriginalFileMarker() + { + elementsOnUndoUntilOriginalFile = int.MinValue; + RecalcIsOriginalFile(); + } + + void FileModified(int newElementsOnUndoStack) + { + if (elementsOnUndoUntilOriginalFile == int.MinValue) + return; + + elementsOnUndoUntilOriginalFile += newElementsOnUndoStack; + if (elementsOnUndoUntilOriginalFile > undostack.Count) + elementsOnUndoUntilOriginalFile = int.MinValue; + + // don't call RecalcIsOriginalFile(): wait until end of undo group + } + #endregion + + /// + /// Gets if the undo stack currently accepts changes. + /// Is false while an undo action is running. + /// + public bool AcceptChanges { + get { return state == StateListen; } + } + + /// + /// Gets if there are actions on the undo stack. + /// Use the PropertyChanged event to listen to changes of this property. + /// + public bool CanUndo { + get { return undostack.Count > 0; } + } + + /// + /// Gets if there are actions on the redo stack. + /// Use the PropertyChanged event to listen to changes of this property. + /// + public bool CanRedo { + get { return redostack.Count > 0; } + } + + /// + /// Gets/Sets the limit on the number of items on the undo stack. + /// + /// The size limit is enforced only on the number of stored top-level undo groups. + /// Elements within undo groups do not count towards the size limit. + public int SizeLimit { + get { return sizeLimit; } + set { + if (value < 0) + ThrowUtil.CheckNotNegative(value, "value"); + if (sizeLimit != value) { + sizeLimit = value; + NotifyPropertyChanged("SizeLimit"); + if (undoGroupDepth == 0) + EnforceSizeLimit(); + } + } + } + + void EnforceSizeLimit() + { + Debug.Assert(undoGroupDepth == 0); + while (undostack.Count > sizeLimit) + undostack.PopFront(); + while (redostack.Count > sizeLimit) + redostack.PopFront(); + } + + /// + /// If an undo group is open, gets the group descriptor of the current top-level + /// undo group. + /// If no undo group is open, gets the group descriptor from the previous undo group. + /// + /// The group descriptor can be used to join adjacent undo groups: + /// use a group descriptor to mark your changes, and on the second action, + /// compare LastGroupDescriptor and use if you + /// want to join the undo groups. + public object LastGroupDescriptor { + get { return lastGroupDescriptor; } + } + + /// + /// Starts grouping changes. + /// Maintains a counter so that nested calls are possible. + /// + public void StartUndoGroup() + { + StartUndoGroup(null); + } + + /// + /// Starts grouping changes. + /// Maintains a counter so that nested calls are possible. + /// + /// An object that is stored with the undo group. + /// If this is not a top-level undo group, the parameter is ignored. + public void StartUndoGroup(object groupDescriptor) + { + if (undoGroupDepth == 0) { + actionCountInUndoGroup = 0; + optionalActionCount = 0; + lastGroupDescriptor = groupDescriptor; + } + undoGroupDepth++; + //Util.LoggingService.Debug("Open undo group (new depth=" + undoGroupDepth + ")"); + } + + /// + /// Starts grouping changes, continuing with the previously closed undo group if possible. + /// Maintains a counter so that nested calls are possible. + /// If the call to StartContinuedUndoGroup is a nested call, it behaves exactly + /// as , only top-level calls can continue existing undo groups. + /// + /// An object that is stored with the undo group. + /// If this is not a top-level undo group, the parameter is ignored. + public void StartContinuedUndoGroup(object groupDescriptor) + { + if (undoGroupDepth == 0) { + actionCountInUndoGroup = (allowContinue && undostack.Count > 0) ? 1 : 0; + optionalActionCount = 0; + lastGroupDescriptor = groupDescriptor; + } + undoGroupDepth++; + //Util.LoggingService.Debug("Continue undo group (new depth=" + undoGroupDepth + ")"); + } + + /// + /// Stops grouping changes. + /// + public void EndUndoGroup() + { + if (undoGroupDepth == 0) return; + undoGroupDepth--; + //Util.LoggingService.Debug("Close undo group (new depth=" + undoGroupDepth + ")"); + if (undoGroupDepth == 0) { + Debug.Assert(state == StateListen || actionCountInUndoGroup == 0); + if (actionCountInUndoGroup == optionalActionCount) { + // only optional actions: don't store them + for (int i = 0; i < optionalActionCount; i++) { + undostack.PopBack(); + } + } else if (actionCountInUndoGroup > 1) { + // combine all actions within the group into a single grouped action + undostack.PushBack(new UndoOperationGroup(undostack, actionCountInUndoGroup)); + FileModified(-actionCountInUndoGroup + 1 + optionalActionCount); + } + //if (state == StateListen) { + EnforceSizeLimit(); + allowContinue = true; + RecalcIsOriginalFile(); // can raise event + //} + } + } + + /// + /// Throws an InvalidOperationException if an undo group is current open. + /// + void ThrowIfUndoGroupOpen() + { + try + { + if (undoGroupDepth != 0) + { + undoGroupDepth = 0; + throw new InvalidOperationException("No undo group should be open at this point"); + } + if (state != StateListen) + { + throw new InvalidOperationException("This method cannot be called while an undo operation is being performed"); + } + } + catch + { + } + } + + List affectedDocuments; + + internal void RegisterAffectedDocument(TextDocument document) + { + if (affectedDocuments == null) + affectedDocuments = new List(); + if (!affectedDocuments.Contains(document)) { + affectedDocuments.Add(document); + document.BeginUpdate(); + } + } + + void CallEndUpdateOnAffectedDocuments() + { + if (affectedDocuments != null) { + foreach (TextDocument doc in affectedDocuments) { + doc.EndUpdate(); + } + affectedDocuments = null; + } + } + + /// + /// Call this method to undo the last operation on the stack + /// + public void Undo() + { + ThrowIfUndoGroupOpen(); + if (undostack.Count > 0) { + // disallow continuing undo groups after undo operation + lastGroupDescriptor = null; allowContinue = false; + // fetch operation to undo and move it to redo stack + IUndoableOperation uedit = undostack.PopBack(); + redostack.PushBack(uedit); + state = StatePlayback; + try { + RunUndo(uedit); + } finally { + state = StateListen; + FileModified(-1); + CallEndUpdateOnAffectedDocuments(); + } + RecalcIsOriginalFile(); + if (undostack.Count == 0) + NotifyPropertyChanged("CanUndo"); + if (redostack.Count == 1) + NotifyPropertyChanged("CanRedo"); + } + } + + internal void RunUndo(IUndoableOperation op) + { + IUndoableOperationWithContext opWithCtx = op as IUndoableOperationWithContext; + if (opWithCtx != null) + opWithCtx.Undo(this); + else + op.Undo(); + } + + /// + /// Call this method to redo the last undone operation + /// + public void Redo() + { + ThrowIfUndoGroupOpen(); + if (redostack.Count > 0) { + lastGroupDescriptor = null; allowContinue = false; + IUndoableOperation uedit = redostack.PopBack(); + undostack.PushBack(uedit); + state = StatePlayback; + try { + RunRedo(uedit); + } finally { + state = StateListen; + FileModified(1); + CallEndUpdateOnAffectedDocuments(); + } + RecalcIsOriginalFile(); + if (redostack.Count == 0) + NotifyPropertyChanged("CanRedo"); + if (undostack.Count == 1) + NotifyPropertyChanged("CanUndo"); + } + } + + internal void RunRedo(IUndoableOperation op) + { + IUndoableOperationWithContext opWithCtx = op as IUndoableOperationWithContext; + if (opWithCtx != null) + opWithCtx.Redo(this); + else + op.Redo(); + } + + /// + /// Call this method to push an UndoableOperation on the undostack. + /// The redostack will be cleared if you use this method. + /// + public void Push(IUndoableOperation operation) + { + Push(operation, false); + } + + /// + /// Call this method to push an UndoableOperation on the undostack. + /// However, the operation will be only stored if the undo group contains a + /// non-optional operation. + /// Use this method to store the caret position/selection on the undo stack to + /// prevent having only actions that affect only the caret and not the document. + /// + public void PushOptional(IUndoableOperation operation) + { + if (undoGroupDepth == 0) + throw new InvalidOperationException("Cannot use PushOptional outside of undo group"); + Push(operation, true); + } + + void Push(IUndoableOperation operation, bool isOptional) + { + if (operation == null) { + throw new ArgumentNullException("operation"); + } + + if (state == StateListen && sizeLimit > 0) { + bool wasEmpty = undostack.Count == 0; + + bool needsUndoGroup = undoGroupDepth == 0; + if (needsUndoGroup) StartUndoGroup(); + undostack.PushBack(operation); + actionCountInUndoGroup++; + if (isOptional) + optionalActionCount++; + else + FileModified(1); + if (needsUndoGroup) EndUndoGroup(); + if (wasEmpty) + NotifyPropertyChanged("CanUndo"); + ClearRedoStack(); + } + } + + /// + /// Call this method, if you want to clear the redo stack + /// + public void ClearRedoStack() + { + if (redostack.Count != 0) { + redostack.Clear(); + NotifyPropertyChanged("CanRedo"); + // if the "original file" marker is on the redo stack: remove it + if (elementsOnUndoUntilOriginalFile < 0) + elementsOnUndoUntilOriginalFile = int.MinValue; + } + } + + /// + /// Clears both the undo and redo stack. + /// + public void ClearAll() + { + ThrowIfUndoGroupOpen(); + actionCountInUndoGroup = 0; + optionalActionCount = 0; + if (undostack.Count != 0) { + lastGroupDescriptor = null; + allowContinue = false; + undostack.Clear(); + NotifyPropertyChanged("CanUndo"); + } + ClearRedoStack(); + } + + internal void Push(TextDocument document, DocumentChangeEventArgs e) + { + if (state == StatePlayback) + throw new InvalidOperationException("Document changes during undo/redo operations are not allowed."); + if (state == StatePlaybackModifyDocument) + state = StatePlayback; // allow only 1 change per expected modification + else + Push(new DocumentChangeOperation(document, e)); + } + + /// + /// Is raised when a property (CanUndo, CanRedo) changed. + /// + public event PropertyChangedEventHandler PropertyChanged; + + void NotifyPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/WeakLineTracker.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/WeakLineTracker.cs new file mode 100644 index 000000000..a70874645 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/WeakLineTracker.cs @@ -0,0 +1,85 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Document +{ + /// + /// Allows registering a line tracker on a TextDocument using a weak reference from the document to the line tracker. + /// + public sealed class WeakLineTracker : ILineTracker + { + TextDocument textDocument; + WeakReference targetObject; + + private WeakLineTracker(TextDocument textDocument, ILineTracker targetTracker) + { + this.textDocument = textDocument; + this.targetObject = new WeakReference(targetTracker); + } + + /// + /// Registers the as line tracker for the . + /// A weak reference to the target tracker will be used, and the WeakLineTracker will deregister itself + /// when the target tracker is garbage collected. + /// + public static WeakLineTracker Register(TextDocument textDocument, ILineTracker targetTracker) + { + if (textDocument == null) + throw new ArgumentNullException("textDocument"); + if (targetTracker == null) + throw new ArgumentNullException("targetTracker"); + WeakLineTracker wlt = new WeakLineTracker(textDocument, targetTracker); + textDocument.LineTrackers.Add(wlt); + return wlt; + } + + /// + /// Deregisters the weak line tracker. + /// + public void Deregister() + { + if (textDocument != null) { + textDocument.LineTrackers.Remove(this); + textDocument = null; + } + } + + void ILineTracker.BeforeRemoveLine(DocumentLine line) + { + ILineTracker targetTracker = targetObject.Target as ILineTracker; + if (targetTracker != null) + targetTracker.BeforeRemoveLine(line); + else + Deregister(); + } + + void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength) + { + ILineTracker targetTracker = targetObject.Target as ILineTracker; + if (targetTracker != null) + targetTracker.SetLineLength(line, newTotalLength); + else + Deregister(); + } + + void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) + { + ILineTracker targetTracker = targetObject.Target as ILineTracker; + if (targetTracker != null) + targetTracker.LineInserted(insertionPos, newLine); + else + Deregister(); + } + + void ILineTracker.RebuildDocument() + { + ILineTracker targetTracker = targetObject.Target as ILineTracker; + if (targetTracker != null) + targetTracker.RebuildDocument(); + else + Deregister(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs new file mode 100644 index 000000000..c82ff94a0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/AbstractMargin.cs @@ -0,0 +1,102 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Base class for margins. + /// Margins don't have to derive from this class, it just helps maintaining a reference to the TextView + /// and the TextDocument. + /// AbstractMargin derives from FrameworkElement, so if you don't want to handle visual children and rendering + /// on your own, choose another base class for your margin! + /// + public abstract class AbstractMargin : FrameworkElement, ITextViewConnect + { + /// + /// TextView property. + /// + public static readonly DependencyProperty TextViewProperty = + DependencyProperty.Register("TextView", typeof(TextView), typeof(AbstractMargin), + new FrameworkPropertyMetadata(OnTextViewChanged)); + + /// + /// Gets/sets the text view for which line numbers are displayed. + /// + /// Adding a margin to will automatically set this property to the text area's TextView. + public TextView TextView { + get { return (TextView)GetValue(TextViewProperty); } + set { SetValue(TextViewProperty, value); } + } + + static void OnTextViewChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + AbstractMargin margin = (AbstractMargin)dp; + margin.wasAutoAddedToTextView = false; + margin.OnTextViewChanged((TextView)e.OldValue, (TextView)e.NewValue); + } + + // automatically set/unset TextView property using ITextViewConnect + bool wasAutoAddedToTextView; + + void ITextViewConnect.AddToTextView(TextView textView) + { + if (this.TextView == null) { + this.TextView = textView; + wasAutoAddedToTextView = true; + } else if (this.TextView != textView) { + throw new InvalidOperationException("This margin belongs to a different TextView."); + } + } + + void ITextViewConnect.RemoveFromTextView(TextView textView) + { + if (wasAutoAddedToTextView && this.TextView == textView) { + this.TextView = null; + Debug.Assert(!wasAutoAddedToTextView); // setting this.TextView should have unset this flag + } + } + + TextDocument document; + + /// + /// Gets the document associated with the margin. + /// + public TextDocument Document { + get { return document; } + } + + /// + /// Called when the is changing. + /// + protected virtual void OnTextViewChanged(TextView oldTextView, TextView newTextView) + { + if (oldTextView != null) { + oldTextView.DocumentChanged -= TextViewDocumentChanged; + } + if (newTextView != null) { + newTextView.DocumentChanged += TextViewDocumentChanged; + } + TextViewDocumentChanged(null, null); + } + + void TextViewDocumentChanged(object sender, EventArgs e) + { + OnDocumentChanged(document, TextView != null ? TextView.Document : null); + } + + /// + /// Called when the is changing. + /// + protected virtual void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument) + { + document = newDocument; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs new file mode 100644 index 000000000..fb21448e5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs @@ -0,0 +1,472 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Helper class with caret-related methods. + /// + public sealed class Caret + { + readonly TextArea textArea; + readonly TextView textView; + readonly CaretLayer caretAdorner; + bool visible; + + internal Caret(TextArea textArea) + { + this.textArea = textArea; + this.textView = textArea.TextView; + position = new TextViewPosition(1, 1, 0); + + caretAdorner = new CaretLayer(textView); + textView.InsertLayer(caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace); + textView.VisualLinesChanged += TextView_VisualLinesChanged; + textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged; + } + + void TextView_VisualLinesChanged(object sender, EventArgs e) + { + if (visible) { + Show(); + } + // required because the visual columns might have changed if the + // element generators did something differently than on the last run + // (e.g. a FoldingSection was collapsed) + InvalidateVisualColumn(); + } + + void TextView_ScrollOffsetChanged(object sender, EventArgs e) + { + if (caretAdorner != null) { + caretAdorner.InvalidateVisual(); + } + } + + double desiredXPos = double.NaN; + TextViewPosition position; + + /// + /// Gets/Sets the position of the caret. + /// Retrieving this property will validate the visual column (which can be expensive). + /// Use the property instead if you don't need the visual column. + /// + public TextViewPosition Position { + get { + ValidateVisualColumn(); + return position; + } + set { + if (position != value) { + position = value; + + storedCaretOffset = -1; + + //Debug.WriteLine("Caret position changing to " + value); + + ValidatePosition(); + InvalidateVisualColumn(); + RaisePositionChanged(); + Log("Caret position changed to " + value); + if (visible) + Show(); + } + } + } + + /// + /// Gets the caret position without validating it. + /// + internal TextViewPosition NonValidatedPosition { + get { + return position; + } + } + + /// + /// Gets/Sets the location of the caret. + /// The getter of this property is faster than because it doesn't have + /// to validate the visual column. + /// + public TextLocation Location { + get { + return position.Location; + } + set { + this.Position = new TextViewPosition(value); + } + } + + /// + /// Gets/Sets the caret line. + /// + public int Line { + get { return position.Line; } + set { + this.Position = new TextViewPosition(value, position.Column); + } + } + + /// + /// Gets/Sets the caret column. + /// + public int Column { + get { return position.Column; } + set { + this.Position = new TextViewPosition(position.Line, value); + } + } + + /// + /// Gets/Sets the caret visual column. + /// + public int VisualColumn { + get { + ValidateVisualColumn(); + return position.VisualColumn; + } + set { + this.Position = new TextViewPosition(position.Line, position.Column, value); + } + } + + bool isInVirtualSpace; + + /// + /// Gets whether the caret is in virtual space. + /// + public bool IsInVirtualSpace { + get { + ValidateVisualColumn(); + return isInVirtualSpace; + } + } + + int storedCaretOffset; + + internal void OnDocumentChanging() + { + storedCaretOffset = this.Offset; + InvalidateVisualColumn(); + } + + internal void OnDocumentChanged(DocumentChangeEventArgs e) + { + InvalidateVisualColumn(); + if (storedCaretOffset >= 0) { + int newCaretOffset = e.GetNewOffset(storedCaretOffset, AnchorMovementType.Default); + TextDocument document = textArea.Document; + if (document != null) { + // keep visual column + this.Position = new TextViewPosition(document.GetLocation(newCaretOffset), position.VisualColumn); + } + } + storedCaretOffset = -1; + } + + /// + /// Gets/Sets the caret offset. + /// Setting the caret offset has the side effect of setting the to NaN. + /// + public int Offset { + get { + TextDocument document = textArea.Document; + if (document == null) { + return 0; + } else { + return document.GetOffset(position.Location); + } + } + set { + TextDocument document = textArea.Document; + if (document != null) { + this.Position = new TextViewPosition(document.GetLocation(value)); + this.DesiredXPos = double.NaN; + } + } + } + + /// + /// Gets/Sets the desired x-position of the caret, in device-independent pixels. + /// This property is NaN if the caret has no desired position. + /// + public double DesiredXPos { + get { return desiredXPos; } + set { desiredXPos = value; } + } + + void ValidatePosition() + { + if (position.Line < 1) + position.Line = 1; + if (position.Column < 1) + position.Column = 1; + if (position.VisualColumn < -1) + position.VisualColumn = -1; + TextDocument document = textArea.Document; + if (document != null) { + if (position.Line > document.LineCount) { + position.Line = document.LineCount; + position.Column = document.GetLineByNumber(position.Line).Length + 1; + position.VisualColumn = -1; + } else { + DocumentLine line = document.GetLineByNumber(position.Line); + if (position.Column > line.Length + 1) { + position.Column = line.Length + 1; + position.VisualColumn = -1; + } + } + } + } + + /// + /// Event raised when the caret position has changed. + /// If the caret position is changed inside a document update (between BeginUpdate/EndUpdate calls), + /// the PositionChanged event is raised only once at the end of the document update. + /// + public event EventHandler PositionChanged; + + bool raisePositionChangedOnUpdateFinished; + + void RaisePositionChanged() + { + if (textArea.Document != null && textArea.Document.IsInUpdate) { + raisePositionChangedOnUpdateFinished = true; + } else { + if (PositionChanged != null) { + PositionChanged(this, EventArgs.Empty); + } + } + } + + internal void OnDocumentUpdateFinished() + { + if (raisePositionChangedOnUpdateFinished) { + if (PositionChanged != null) { + PositionChanged(this, EventArgs.Empty); + } + } + } + + bool visualColumnValid; + + void ValidateVisualColumn() + { + if (!visualColumnValid) { + TextDocument document = textArea.Document; + if (document != null) { + //Debug.WriteLine("Explicit validation of caret column"); + var documentLine = document.GetLineByNumber(position.Line); + RevalidateVisualColumn(textView.GetOrConstructVisualLine(documentLine)); + } + } + } + + void InvalidateVisualColumn() + { + visualColumnValid = false; + } + + /// + /// Validates the visual column of the caret using the specified visual line. + /// The visual line must contain the caret offset. + /// + void RevalidateVisualColumn(VisualLine visualLine) + { + if (visualLine == null) + throw new ArgumentNullException("visualLine"); + + // mark column as validated + visualColumnValid = true; + + int caretOffset = textView.Document.GetOffset(position.Location); + int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset; + position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace); + + // search possible caret positions + int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); + // If position.VisualColumn was valid, we're done with validation. + if (newVisualColumnForwards != position.VisualColumn) { + // also search backwards so that we can pick the better match + int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); + + if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0) + throw ThrowUtil.NoValidCaretPosition(); + + // determine offsets for new visual column positions + int newOffsetForwards; + if (newVisualColumnForwards >= 0) + newOffsetForwards = visualLine.GetRelativeOffset(newVisualColumnForwards) + firstDocumentLineOffset; + else + newOffsetForwards = -1; + int newOffsetBackwards; + if (newVisualColumnBackwards >= 0) + newOffsetBackwards = visualLine.GetRelativeOffset(newVisualColumnBackwards) + firstDocumentLineOffset; + else + newOffsetBackwards = -1; + + int newVisualColumn, newOffset; + // if there's only one valid position, use it + if (newVisualColumnForwards < 0) { + newVisualColumn = newVisualColumnBackwards; + newOffset = newOffsetBackwards; + } else if (newVisualColumnBackwards < 0) { + newVisualColumn = newVisualColumnForwards; + newOffset = newOffsetForwards; + } else { + // two valid positions: find the better match + if (Math.Abs(newOffsetBackwards - caretOffset) < Math.Abs(newOffsetForwards - caretOffset)) { + // backwards is better + newVisualColumn = newVisualColumnBackwards; + newOffset = newOffsetBackwards; + } else { + // forwards is better + newVisualColumn = newVisualColumnForwards; + newOffset = newOffsetForwards; + } + } + this.Position = new TextViewPosition(textView.Document.GetLocation(newOffset), newVisualColumn); + } + isInVirtualSpace = (position.VisualColumn > visualLine.VisualLength); + } + + Rect CalcCaretRectangle(VisualLine visualLine) + { + if (!visualColumnValid) { + RevalidateVisualColumn(visualLine); + } + + TextLine textLine = visualLine.GetTextLine(position.VisualColumn); + double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn); + double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop); + double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom); + + return new Rect(xPos, + lineTop, + SystemParameters.CaretWidth, + lineBottom - lineTop); + } + + /// + /// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document. + /// + public Rect CalculateCaretRectangle() + { + if (textView != null && textView.Document != null) { + VisualLine visualLine = textView.GetOrConstructVisualLine(textView.Document.GetLineByNumber(position.Line)); + return CalcCaretRectangle(visualLine); + } else { + return Rect.Empty; + } + } + + /// + /// Minimum distance of the caret to the view border. + /// + internal const double MinimumDistanceToViewBorder = 30; + + /// + /// Scrolls the text view so that the caret is visible. + /// + public void BringCaretToView() + { + BringCaretToView(MinimumDistanceToViewBorder); + } + + internal void BringCaretToView(double border) + { + Rect caretRectangle = CalculateCaretRectangle(); + if (!caretRectangle.IsEmpty) { + caretRectangle.Inflate(border, border); + textView.MakeVisible(caretRectangle); + } + } + + /// + /// Makes the caret visible and updates its on-screen position. + /// + public void Show() + { + Log("Caret.Show()"); + visible = true; + if (!showScheduled) { + showScheduled = true; + textArea.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(ShowInternal)); + } + } + + bool showScheduled; + bool hasWin32Caret; + + void ShowInternal() + { + showScheduled = false; + + // if show was scheduled but caret hidden in the meantime + if (!visible) + return; + + if (caretAdorner != null && textView != null) { + VisualLine visualLine = textView.GetVisualLine(position.Line); + if (visualLine != null) { + Rect caretRect = CalcCaretRectangle(visualLine); + // Create Win32 caret so that Windows knows where our managed caret is. This is necessary for + // features like 'Follow text editing' in the Windows Magnifier. + if (!hasWin32Caret) { + hasWin32Caret = Win32.CreateCaret(textView, caretRect.Size); + } + if (hasWin32Caret) { + Win32.SetCaretPosition(textView, caretRect.Location - textView.ScrollOffset); + } + caretAdorner.Show(caretRect); + textArea.ime.UpdateCompositionWindow(); + } else { + caretAdorner.Hide(); + } + } + } + + /// + /// Makes the caret invisible. + /// + public void Hide() + { + Log("Caret.Hide()"); + visible = false; + if (hasWin32Caret) { + Win32.DestroyCaret(); + hasWin32Caret = false; + } + if (caretAdorner != null) { + caretAdorner.Hide(); + } + } + + [Conditional("DEBUG")] + static void Log(string text) + { + // commented out to make debug output less noisy - add back if there are any problems with the caret + //Debug.WriteLine(text); + } + + /// + /// Gets/Sets the color of the caret. + /// + public Brush CaretBrush { + get { return caretAdorner.CaretBrush; } + set { caretAdorner.CaretBrush = value; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs new file mode 100644 index 000000000..105f38d16 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretLayer.cs @@ -0,0 +1,86 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + sealed class CaretLayer : Layer + { + bool isVisible; + Rect caretRectangle; + + DispatcherTimer caretBlinkTimer = new DispatcherTimer(); + bool blink; + + public CaretLayer(TextView textView) : base(textView, KnownLayer.Caret) + { + this.IsHitTestVisible = false; + caretBlinkTimer.Tick += new EventHandler(caretBlinkTimer_Tick); + } + + void caretBlinkTimer_Tick(object sender, EventArgs e) + { + blink = !blink; + InvalidateVisual(); + } + + public void Show(Rect caretRectangle) + { + this.caretRectangle = caretRectangle; + this.isVisible = true; + StartBlinkAnimation(); + InvalidateVisual(); + } + + public void Hide() + { + if (isVisible) { + isVisible = false; + StopBlinkAnimation(); + InvalidateVisual(); + } + } + + void StartBlinkAnimation() + { + TimeSpan blinkTime = Win32.CaretBlinkTime; + blink = true; // the caret should visible initially + // This is important if blinking is disabled (system reports a negative blinkTime) + if (blinkTime.TotalMilliseconds > 0) { + caretBlinkTimer.Interval = blinkTime; + caretBlinkTimer.Start(); + } + } + + void StopBlinkAnimation() + { + caretBlinkTimer.Stop(); + } + + internal Brush CaretBrush; + + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + if (isVisible && blink) { + Brush caretBrush = this.CaretBrush; + if (caretBrush == null) + caretBrush = (Brush)textView.GetValue(TextBlock.ForegroundProperty); + Rect r = new Rect(caretRectangle.X - textView.HorizontalOffset, + caretRectangle.Y - textView.VerticalOffset, + caretRectangle.Width, + caretRectangle.Height); + drawingContext.DrawRectangle(caretBrush, null, PixelSnapHelpers.Round(r, PixelSnapHelpers.GetPixelSize(this))); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs new file mode 100644 index 000000000..d1b812b66 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretNavigationCommandHandler.cs @@ -0,0 +1,375 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + static class CaretNavigationCommandHandler + { + /// + /// Creates a new for the text area. + /// + public static TextAreaInputHandler Create(TextArea textArea) + { + TextAreaInputHandler handler = new TextAreaInputHandler(textArea); + handler.CommandBindings.AddRange(CommandBindings); + handler.InputBindings.AddRange(InputBindings); + return handler; + } + + static readonly List CommandBindings = new List(); + static readonly List InputBindings = new List(); + + static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler) + { + CommandBindings.Add(new CommandBinding(command, handler)); + InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key)); + } + + static CaretNavigationCommandHandler() + { + const ModifierKeys None = ModifierKeys.None; + const ModifierKeys Ctrl = ModifierKeys.Control; + const ModifierKeys Shift = ModifierKeys.Shift; + const ModifierKeys Alt = ModifierKeys.Alt; + + AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft)); + AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft)); + AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft)); + AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight)); + AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight)); + AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight)); + + AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft)); + AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft)); + AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft)); + AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight)); + AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight)); + AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight)); + + AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp)); + AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp)); + AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp)); + AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown)); + AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown)); + AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown)); + + AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown)); + AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown)); + AddBinding(EditingCommands.MoveUpByPage, None, Key.PageUp, OnMoveCaret(CaretMovementType.PageUp)); + AddBinding(EditingCommands.SelectUpByPage, Shift, Key.PageUp, OnMoveCaretExtendSelection(CaretMovementType.PageUp)); + + AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart)); + AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart)); + AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart)); + AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd)); + AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd)); + AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd)); + + AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart)); + AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart)); + AddBinding(EditingCommands.MoveToDocumentEnd, Ctrl, Key.End, OnMoveCaret(CaretMovementType.DocumentEnd)); + AddBinding(EditingCommands.SelectToDocumentEnd, Ctrl | Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.DocumentEnd)); + + CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, OnSelectAll)); + + TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings); + } + + static void OnSelectAll(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + textArea.Caret.Offset = textArea.Document.TextLength; + textArea.Selection = SimpleSelection.Create(textArea, 0, textArea.Document.TextLength); + } + } + + static TextArea GetTextArea(object target) + { + return target as TextArea; + } + + enum CaretMovementType + { + CharLeft, + CharRight, + WordLeft, + WordRight, + LineUp, + LineDown, + PageUp, + PageDown, + LineStart, + LineEnd, + DocumentStart, + DocumentEnd + } + + static ExecutedRoutedEventHandler OnMoveCaret(CaretMovementType direction) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + textArea.ClearSelection(); + MoveCaret(textArea, direction); + textArea.Caret.BringCaretToView(); + } + }; + } + + static ExecutedRoutedEventHandler OnMoveCaretExtendSelection(CaretMovementType direction) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + TextViewPosition oldPosition = textArea.Caret.Position; + MoveCaret(textArea, direction); + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + if (!textArea.Document.IsInUpdate) // if we're inside a larger update (e.g. called by EditingCommandHandler.OnDelete()), avoid calculating the caret rectangle now + textArea.Caret.BringCaretToView(); + } + }; + } + + static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.Handled = true; + // First, convert the selection into a rectangle selection + // (this is required so that virtual space gets enabled for the caret movement) + if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) { + if (textArea.Selection.IsEmpty) { + textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position); + } else { + // Convert normal selection to rectangle selection + textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position); + } + } + // Now move the caret and extend the selection + TextViewPosition oldPosition = textArea.Caret.Position; + MoveCaret(textArea, direction); + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + textArea.Caret.BringCaretToView(); + } + }; + } + + #region Caret movement + static void MoveCaret(TextArea textArea, CaretMovementType direction) + { + DocumentLine caretLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); + VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(caretLine); + TextViewPosition caretPosition = textArea.Caret.Position; + TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn); + switch (direction) { + case CaretMovementType.CharLeft: + MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.Normal); + break; + case CaretMovementType.CharRight: + MoveCaretRight(textArea, caretPosition, visualLine, CaretPositioningMode.Normal); + break; + case CaretMovementType.WordLeft: + MoveCaretLeft(textArea, caretPosition, visualLine, CaretPositioningMode.WordStart); + break; + case CaretMovementType.WordRight: + MoveCaretRight(textArea, caretPosition, visualLine, CaretPositioningMode.WordStart); + break; + case CaretMovementType.LineUp: + case CaretMovementType.LineDown: + case CaretMovementType.PageUp: + case CaretMovementType.PageDown: + MoveCaretUpDown(textArea, direction, visualLine, textLine, caretPosition.VisualColumn); + break; + case CaretMovementType.DocumentStart: + SetCaretPosition(textArea, 0, 0); + break; + case CaretMovementType.DocumentEnd: + SetCaretPosition(textArea, -1, textArea.Document.TextLength); + break; + case CaretMovementType.LineStart: + MoveCaretToStartOfLine(textArea, visualLine); + break; + case CaretMovementType.LineEnd: + MoveCaretToEndOfLine(textArea, visualLine); + break; + default: + throw new NotSupportedException(direction.ToString()); + } + } + #endregion + + #region Home/End + static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine) + { + int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart, textArea.Selection.EnableVirtualSpace); + if (newVC < 0) + throw ThrowUtil.NoValidCaretPosition(); + // when the caret is already at the start of the text, jump to start before whitespace + if (newVC == textArea.Caret.VisualColumn) + newVC = 0; + int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC); + SetCaretPosition(textArea, newVC, offset); + } + + static void MoveCaretToEndOfLine(TextArea textArea, VisualLine visualLine) + { + int newVC = visualLine.VisualLength; + int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC); + SetCaretPosition(textArea, newVC, offset); + } + #endregion + + #region By-character / By-word movement + static void MoveCaretRight(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) + { + int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); + if (pos >= 0) { + SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); + } else { + // move to start of next line + DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine; + if (nextDocumentLine != null) { + VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine); + pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); + if (pos < 0) + throw ThrowUtil.NoValidCaretPosition(); + SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset); + } else { + // at end of document + Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textArea.Document.TextLength); + SetCaretPosition(textArea, -1, textArea.Document.TextLength); + } + } + } + + static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) + { + int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); + if (pos >= 0) { + SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); + } else { + // move to end of previous line + DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; + if (previousDocumentLine != null) { + VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine); + pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); + if (pos < 0) + throw ThrowUtil.NoValidCaretPosition(); + SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset); + } else { + // at start of document + Debug.Assert(visualLine.FirstDocumentLine.Offset == 0); + SetCaretPosition(textArea, 0, 0); + } + } + } + #endregion + + #region Line+Page up/down + static void MoveCaretUpDown(TextArea textArea, CaretMovementType direction, VisualLine visualLine, TextLine textLine, int caretVisualColumn) + { + // moving up/down happens using the desired visual X position + double xPos = textArea.Caret.DesiredXPos; + if (double.IsNaN(xPos)) + xPos = visualLine.GetTextLineVisualXPosition(textLine, caretVisualColumn); + // now find the TextLine+VisualLine where the caret will end up in + VisualLine targetVisualLine = visualLine; + TextLine targetLine; + int textLineIndex = visualLine.TextLines.IndexOf(textLine); + switch (direction) { + case CaretMovementType.LineUp: + { + // Move up: move to the previous TextLine in the same visual line + // or move to the last TextLine of the previous visual line + int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1; + if (textLineIndex > 0) { + targetLine = visualLine.TextLines[textLineIndex - 1]; + } else if (prevLineNumber >= 1) { + DocumentLine prevLine = textArea.Document.GetLineByNumber(prevLineNumber); + targetVisualLine = textArea.TextView.GetOrConstructVisualLine(prevLine); + targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1]; + } else { + targetLine = null; + } + break; + } + case CaretMovementType.LineDown: + { + // Move down: move to the next TextLine in the same visual line + // or move to the first TextLine of the next visual line + int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1; + if (textLineIndex < visualLine.TextLines.Count - 1) { + targetLine = visualLine.TextLines[textLineIndex + 1]; + } else if (nextLineNumber <= textArea.Document.LineCount) { + DocumentLine nextLine = textArea.Document.GetLineByNumber(nextLineNumber); + targetVisualLine = textArea.TextView.GetOrConstructVisualLine(nextLine); + targetLine = targetVisualLine.TextLines[0]; + } else { + targetLine = null; + } + break; + } + case CaretMovementType.PageUp: + case CaretMovementType.PageDown: + { + // Page up/down: find the target line using its visual position + double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle); + if (direction == CaretMovementType.PageUp) + yPos -= textArea.TextView.RenderSize.Height; + else + yPos += textArea.TextView.RenderSize.Height; + DocumentLine newLine = textArea.TextView.GetDocumentLineByVisualTop(yPos); + targetVisualLine = textArea.TextView.GetOrConstructVisualLine(newLine); + targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos); + break; + } + default: + throw new NotSupportedException(direction.ToString()); + } + if (targetLine != null) { + double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle); + int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), textArea.Selection.EnableVirtualSpace); + SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false); + textArea.Caret.DesiredXPos = xPos; + } + } + #endregion + + #region SetCaretPosition + static void SetCaretPosition(TextArea textArea, VisualLine targetVisualLine, TextLine targetLine, + int newVisualColumn, bool allowWrapToNextLine) + { + int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine); + if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length) { + if (newVisualColumn <= targetVisualLine.VisualLength) + newVisualColumn = targetLineStartCol + targetLine.Length - 1; + } + int newOffset = targetVisualLine.GetRelativeOffset(newVisualColumn) + targetVisualLine.FirstDocumentLine.Offset; + SetCaretPosition(textArea, newVisualColumn, newOffset); + } + + static void SetCaretPosition(TextArea textArea, int newVisualColumn, int newOffset) + { + textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(newOffset), newVisualColumn); + textArea.Caret.DesiredXPos = double.NaN; + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs new file mode 100644 index 000000000..ebf5e4947 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/CaretWeakEventHandler.cs @@ -0,0 +1,33 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Contains classes for handling weak events on the Caret class. + /// + public static class CaretWeakEventManager + { + /// + /// Handles the Caret.PositionChanged event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class PositionChanged : WeakEventManagerBase + { + /// + protected override void StartListening(Caret source) + { + source.PositionChanged += DeliverEvent; + } + + /// + protected override void StopListening(Caret source) + { + source.PositionChanged -= DeliverEvent; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs new file mode 100644 index 000000000..3de848dda --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DottedLineMargin.cs @@ -0,0 +1,63 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Margin for use with the text area. + /// A vertical dotted line to separate the line numbers from the text view. + /// + public static class DottedLineMargin + { + static readonly object tag = new object(); + + /// + /// Creates a vertical dotted line to separate the line numbers from the text view. + /// + public static UIElement Create() + { + Line line = new Line { + //X1 = 0, Y1 = 0, X2 = 0, Y2 = 1, + //StrokeDashArray = { 0, 2 }, + //Stretch = Stretch.Fill, + //StrokeThickness = 1, + //StrokeDashCap = PenLineCap.Round, + //Margin = new Thickness(2, 0, 2, 0), + //Tag = tag + }; + + return line; + } + + /// + /// Creates a vertical dotted line to separate the line numbers from the text view. + /// + [Obsolete("This method got published accidentally; and will be removed again in a future version. Use the parameterless overload instead.")] + public static UIElement Create(TextEditor editor) + { + Line line = (Line)Create(); + + line.SetBinding( + Line.StrokeProperty, + new Binding("LineNumbersForeground") { Source = editor } + ); + + return line; + } + + /// + /// Gets whether the specified UIElement is the result of a DottedLineMargin.Create call. + /// + public static bool IsDottedLineMargin(UIElement element) + { + Line l = element as Line; + return l != null && l.Tag == tag; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs new file mode 100644 index 000000000..2ba8e41ec --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/DragDropException.cs @@ -0,0 +1,46 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Runtime.Serialization; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Wraps exceptions that occur during drag'n'drop. + /// Exceptions during drag'n'drop might + /// get swallowed by WPF/COM, so AvalonEdit catches them and re-throws them later + /// wrapped in a DragDropException. + /// + [Serializable()] + public class DragDropException : Exception + { + /// + /// Creates a new DragDropException. + /// + public DragDropException() : base() + { + } + + /// + /// Creates a new DragDropException. + /// + public DragDropException(string message) : base(message) + { + } + + /// + /// Creates a new DragDropException. + /// + public DragDropException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Deserializes a DragDropException. + /// + protected DragDropException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs new file mode 100644 index 000000000..685e7ae31 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EditingCommandHandler.cs @@ -0,0 +1,578 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Highlighting; +using Tango.Scripting.Editors.Search; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// We re-use the CommandBinding and InputBinding instances between multiple text areas, + /// so this class is static. + /// + static class EditingCommandHandler + { + /// + /// Creates a new for the text area. + /// + public static TextAreaInputHandler Create(TextArea textArea) + { + TextAreaInputHandler handler = new TextAreaInputHandler(textArea); + handler.CommandBindings.AddRange(CommandBindings); + handler.InputBindings.AddRange(InputBindings); + return handler; + } + + static readonly List CommandBindings = new List(); + static readonly List InputBindings = new List(); + + static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler) + { + CommandBindings.Add(new CommandBinding(command, handler)); + InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key)); + } + + static EditingCommandHandler() + { + CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, OnDelete(ApplicationCommands.NotACommand), CanDelete)); + AddBinding(EditingCommands.Delete, ModifierKeys.None, Key.Delete, OnDelete(EditingCommands.SelectRightByCharacter)); + AddBinding(EditingCommands.DeleteNextWord, ModifierKeys.Control, Key.Delete, OnDelete(EditingCommands.SelectRightByWord)); + AddBinding(EditingCommands.Backspace, ModifierKeys.None, Key.Back, OnDelete(EditingCommands.SelectLeftByCharacter)); + InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(EditingCommands.Backspace, ModifierKeys.Shift, Key.Back)); // make Shift-Backspace do the same as plain backspace + AddBinding(EditingCommands.DeletePreviousWord, ModifierKeys.Control, Key.Back, OnDelete(EditingCommands.SelectLeftByWord)); + AddBinding(EditingCommands.EnterParagraphBreak, ModifierKeys.None, Key.Enter, OnEnter); + AddBinding(EditingCommands.EnterLineBreak, ModifierKeys.Shift, Key.Enter, OnEnter); + AddBinding(EditingCommands.TabForward, ModifierKeys.None, Key.Tab, OnTab); + AddBinding(EditingCommands.TabBackward, ModifierKeys.Shift, Key.Tab, OnShiftTab); + + CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy)); + CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy)); + CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste)); + + CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine)); + + CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToUppercase, OnConvertToUpperCase)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToLowercase, OnConvertToLowerCase)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToTitleCase, OnConvertToTitleCase)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.InvertCase, OnInvertCase)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertTabsToSpaces, OnConvertTabsToSpaces)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertSpacesToTabs, OnConvertSpacesToTabs)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.IndentSelection, OnIndentSelection)); + + TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings); + } + + static TextArea GetTextArea(object target) + { + return target as TextArea; + } + + #region Text Transformation Helpers + enum DefaultSegmentType + { + None, + WholeDocument, + CurrentLine + } + + /// + /// Calls transformLine on all lines in the selected range. + /// transformLine needs to handle read-only segments! + /// + static void TransformSelectedLines(Action transformLine, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + using (textArea.Document.RunUpdate()) { + DocumentLine start, end; + if (textArea.Selection.IsEmpty) { + if (defaultSegmentType == DefaultSegmentType.CurrentLine) { + start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line); + } else if (defaultSegmentType == DefaultSegmentType.WholeDocument) { + start = textArea.Document.Lines.First(); + end = textArea.Document.Lines.Last(); + } else { + start = end = null; + } + } else { + ISegment segment = textArea.Selection.SurroundingSegment; + start = textArea.Document.GetLineByOffset(segment.Offset); + end = textArea.Document.GetLineByOffset(segment.EndOffset); + // don't include the last line if no characters on it are selected + if (start != end && end.Offset == segment.EndOffset) + end = end.PreviousLine; + } + if (start != null) { + transformLine(textArea, start); + while (start != end) { + start = start.NextLine; + transformLine(textArea, start); + } + } + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + } + + /// + /// Calls transformLine on all writable segment in the selected range. + /// + static void TransformSelectedSegments(Action transformSegment, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + using (textArea.Document.RunUpdate()) { + IEnumerable segments; + if (textArea.Selection.IsEmpty) { + if (defaultSegmentType == DefaultSegmentType.CurrentLine) { + segments = new ISegment[] { textArea.Document.GetLineByNumber(textArea.Caret.Line) }; + } else if (defaultSegmentType == DefaultSegmentType.WholeDocument) { + segments = textArea.Document.Lines.Cast(); + } else { + segments = null; + } + } else { + segments = textArea.Selection.Segments.Cast(); + } + if (segments != null) { + foreach (ISegment segment in segments.Reverse()) { + foreach (ISegment writableSegment in textArea.GetDeletableSegments(segment).Reverse()) { + transformSegment(textArea, writableSegment); + } + } + } + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + } + #endregion + + #region EnterLineBreak + static void OnEnter(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.IsKeyboardFocused) { + textArea.PerformTextInput("\n"); + args.Handled = true; + } + } + #endregion + + #region Tab + static void OnTab(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + using (textArea.Document.RunUpdate()) { + if (textArea.Selection.IsMultiline) { + var segment = textArea.Selection.SurroundingSegment; + DocumentLine start = textArea.Document.GetLineByOffset(segment.Offset); + DocumentLine end = textArea.Document.GetLineByOffset(segment.EndOffset); + // don't include the last line if no characters on it are selected + if (start != end && end.Offset == segment.EndOffset) + end = end.PreviousLine; + DocumentLine current = start; + while (true) { + int offset = current.Offset; + if (textArea.ReadOnlySectionProvider.CanInsert(offset)) + textArea.Document.Replace(offset, 0, textArea.Options.IndentationString, OffsetChangeMappingType.KeepAnchorBeforeInsertion); + if (current == end) + break; + current = current.NextLine; + } + } else { + string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column); + textArea.ReplaceSelectionWithText(indentationString); + } + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + } + + static void OnShiftTab(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedLines( + delegate (TextArea textArea, DocumentLine line) { + int offset = line.Offset; + ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize); + if (s.Length > 0) { + s = textArea.GetDeletableSegments(s).FirstOrDefault(); + if (s != null && s.Length > 0) { + textArea.Document.Remove(s.Offset, s.Length); + } + } + }, target, args, DefaultSegmentType.CurrentLine); + } + #endregion + + #region Delete + static ExecutedRoutedEventHandler OnDelete(RoutedUICommand selectingCommand) + { + return (target, args) => { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + // call BeginUpdate before running the 'selectingCommand' + // so that undoing the delete does not select the deleted character + using (textArea.Document.RunUpdate()) { + if (textArea.Selection.IsEmpty) { + TextViewPosition oldCaretPosition = textArea.Caret.Position; + if (textArea.Caret.IsInVirtualSpace && selectingCommand == EditingCommands.SelectRightByCharacter) + EditingCommands.SelectRightByWord.Execute(args.Parameter, textArea); + else + selectingCommand.Execute(args.Parameter, textArea); + bool hasSomethingDeletable = false; + foreach (ISegment s in textArea.Selection.Segments) { + if (textArea.GetDeletableSegments(s).Length > 0) { + hasSomethingDeletable = true; + break; + } + } + if (!hasSomethingDeletable) { + // If nothing in the selection is deletable; then reset caret+selection + // to the previous value. This prevents the caret from moving through read-only sections. + textArea.Caret.Position = oldCaretPosition; + textArea.ClearSelection(); + } + } + textArea.RemoveSelectedText(); + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + }; + } + + static void CanDelete(object target, CanExecuteRoutedEventArgs args) + { + // HasSomethingSelected for delete command + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.CanExecute = !textArea.Selection.IsEmpty; + args.Handled = true; + } + } + #endregion + + #region Clipboard commands + static void CanCutOrCopy(object target, CanExecuteRoutedEventArgs args) + { + // HasSomethingSelected for copy and cut commands + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.CanExecute = textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty; + args.Handled = true; + } + } + + static void OnCopy(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) { + DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); + CopyWholeLine(textArea, currentLine); + } else { + CopySelectedText(textArea); + } + args.Handled = true; + } + } + + static void OnCut(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) { + DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); + CopyWholeLine(textArea, currentLine); + ISegment[] segmentsToDelete = textArea.GetDeletableSegments(new SimpleSegment(currentLine.Offset, currentLine.TotalLength)); + for (int i = segmentsToDelete.Length - 1; i >= 0; i--) { + textArea.Document.Remove(segmentsToDelete[i]); + } + } else { + CopySelectedText(textArea); + textArea.RemoveSelectedText(); + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + } + + static void CopySelectedText(TextArea textArea) + { + var data = textArea.Selection.CreateDataObject(textArea); + + try { + Clipboard.SetDataObject(data, true); + } catch (ExternalException) { + // Apparently this exception sometimes happens randomly. + // The MS controls just ignore it, so we'll do the same. + return; + } + + string text = textArea.Selection.GetText(); + text = TextUtilities.NormalizeNewLines(text, Environment.NewLine); + textArea.OnTextCopied(new TextEventArgs(text)); + } + + const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy + + static void CopyWholeLine(TextArea textArea, DocumentLine line) + { + ISegment wholeLine = new SimpleSegment(line.Offset, line.TotalLength); + string text = textArea.Document.GetText(wholeLine); + // Ensure we use the appropriate newline sequence for the OS + text = TextUtilities.NormalizeNewLines(text, Environment.NewLine); + DataObject data = new DataObject(text); + + // Also copy text in HTML format to clipboard - good for pasting text into Word + // or to the SharpDevelop forums. + IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter; + HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, wholeLine, new HtmlOptions(textArea.Options))); + + MemoryStream lineSelected = new MemoryStream(1); + lineSelected.WriteByte(1); + data.SetData(LineSelectedType, lineSelected, false); + + try { + Clipboard.SetDataObject(data, true); + } catch (ExternalException) { + // Apparently this exception sometimes happens randomly. + // The MS controls just ignore it, so we'll do the same. + return; + } + textArea.OnTextCopied(new TextEventArgs(text)); + } + + static void CanPaste(object target, CanExecuteRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + args.CanExecute = textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset) + && Clipboard.ContainsText(); + // WPF Clipboard.ContainsText() is safe to call without catching ExternalExceptions + // because it doesn't try to lock the clipboard - it just peeks inside with IsClipboardFormatAvailable(). + args.Handled = true; + } + } + + static void OnPaste(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + IDataObject dataObject; + try { + dataObject = Clipboard.GetDataObject(); + } catch (ExternalException) { + return; + } + if (dataObject == null) + return; + Debug.WriteLine( dataObject.GetData(DataFormats.Html) as string ); + + // convert text back to correct newlines for this document + string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line); + string text; + try { + text = (string)dataObject.GetData(DataFormats.UnicodeText); + text = TextUtilities.NormalizeNewLines(text, newLine); + } catch (OutOfMemoryException) { + return; + } + + if (!string.IsNullOrEmpty(text)) { + bool fullLine = textArea.Options.CutCopyWholeLine && dataObject.GetDataPresent(LineSelectedType); + bool rectangular = dataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType); + if (fullLine) { + DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); + if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) { + textArea.Document.Insert(currentLine.Offset, text); + } + } else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) { + if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false)) + textArea.ReplaceSelectionWithText(text); + } else { + textArea.ReplaceSelectionWithText(text); + } + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + } + #endregion + + #region DeleteLine + static void OnDeleteLine(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); + textArea.Selection = Selection.Create(textArea, currentLine.Offset, currentLine.Offset + currentLine.TotalLength); + textArea.RemoveSelectedText(); + args.Handled = true; + } + } + #endregion + + #region Remove..Whitespace / Convert Tabs-Spaces + static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedLines( + delegate (TextArea textArea, DocumentLine line) { + textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line)); + }, target, args, DefaultSegmentType.WholeDocument); + } + + static void OnRemoveTrailingWhitespace(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedLines( + delegate (TextArea textArea, DocumentLine line) { + textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line)); + }, target, args, DefaultSegmentType.WholeDocument); + } + + static void OnConvertTabsToSpaces(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedSegments(ConvertTabsToSpaces, target, args, DefaultSegmentType.WholeDocument); + } + + static void OnConvertLeadingTabsToSpaces(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedLines( + delegate (TextArea textArea, DocumentLine line) { + ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line)); + }, target, args, DefaultSegmentType.WholeDocument); + } + + static void ConvertTabsToSpaces(TextArea textArea, ISegment segment) + { + TextDocument document = textArea.Document; + int endOffset = segment.EndOffset; + string indentationString = new string(' ', textArea.Options.IndentationSize); + for (int offset = segment.Offset; offset < endOffset; offset++) { + if (document.GetCharAt(offset) == '\t') { + document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace); + endOffset += indentationString.Length - 1; + } + } + } + + static void OnConvertSpacesToTabs(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedSegments(ConvertSpacesToTabs, target, args, DefaultSegmentType.WholeDocument); + } + + static void OnConvertLeadingSpacesToTabs(object target, ExecutedRoutedEventArgs args) + { + TransformSelectedLines( + delegate (TextArea textArea, DocumentLine line) { + ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line)); + }, target, args, DefaultSegmentType.WholeDocument); + } + + static void ConvertSpacesToTabs(TextArea textArea, ISegment segment) + { + TextDocument document = textArea.Document; + int endOffset = segment.EndOffset; + int indentationSize = textArea.Options.IndentationSize; + int spacesCount = 0; + for (int offset = segment.Offset; offset < endOffset; offset++) { + if (document.GetCharAt(offset) == ' ') { + spacesCount++; + if (spacesCount == indentationSize) { + document.Replace(offset - (indentationSize - 1), indentationSize, "\t", OffsetChangeMappingType.CharacterReplace); + spacesCount = 0; + offset -= indentationSize - 1; + endOffset -= indentationSize - 1; + } + } else { + spacesCount = 0; + } + } + } + #endregion + + #region Convert...Case + static void ConvertCase(Func transformText, object target, ExecutedRoutedEventArgs args) + { + TransformSelectedSegments( + delegate (TextArea textArea, ISegment segment) { + string oldText = textArea.Document.GetText(segment); + string newText = transformText(oldText); + textArea.Document.Replace(segment.Offset, segment.Length, newText, OffsetChangeMappingType.CharacterReplace); + }, target, args, DefaultSegmentType.WholeDocument); + } + + static void OnConvertToUpperCase(object target, ExecutedRoutedEventArgs args) + { + ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, target, args); + } + + static void OnConvertToLowerCase(object target, ExecutedRoutedEventArgs args) + { + ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, target, args); + } + + static void OnConvertToTitleCase(object target, ExecutedRoutedEventArgs args) + { + ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args); + } + + static void OnInvertCase(object target, ExecutedRoutedEventArgs args) + { + ConvertCase(InvertCase, target, args); + } + + static string InvertCase(string text) + { + CultureInfo culture = CultureInfo.CurrentCulture; + char[] buffer = text.ToCharArray(); + for (int i = 0; i < buffer.Length; ++i) { + char c = buffer[i]; + buffer[i] = char.IsUpper(c) ? char.ToLower(c, culture) : char.ToUpper(c, culture); + } + return new string(buffer); + } + #endregion + + static void OnIndentSelection(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + using (textArea.Document.RunUpdate()) { + int start, end; + if (textArea.Selection.IsEmpty) { + start = 1; + end = textArea.Document.LineCount; + } else { + start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber; + end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber; + } + textArea.IndentationStrategy.IndentLines(textArea.Document, start, end); + } + textArea.Caret.BringCaretToView(); + args.Handled = true; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs new file mode 100644 index 000000000..6cca63ecb --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/EmptySelection.cs @@ -0,0 +1,85 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + sealed class EmptySelection : Selection + { + public EmptySelection(TextArea textArea) : base(textArea) + { + } + + public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e) + { + return this; + } + + public override TextViewPosition StartPosition { + get { return new TextViewPosition(TextLocation.Empty); } + } + + public override TextViewPosition EndPosition { + get { return new TextViewPosition(TextLocation.Empty); } + } + + public override ISegment SurroundingSegment { + get { return null; } + } + + public override Selection SetEndpoint(TextViewPosition endPosition) + { + throw new NotSupportedException(); + } + + public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition) + { + var document = textArea.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + return Create(textArea, startPosition, endPosition); + } + + public override IEnumerable Segments { + get { return Empty.Array; } + } + + public override string GetText() + { + return string.Empty; + } + + public override void ReplaceSelectionWithText(string newText) + { + if (newText == null) + throw new ArgumentNullException("newText"); + newText = AddSpacesIfRequired(newText, textArea.Caret.Position, textArea.Caret.Position); + if (newText.Length > 0) { + if (textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset)) { + textArea.Document.Insert(textArea.Caret.Offset, newText); + } + } + textArea.Caret.VisualColumn = -1; + } + + public override int Length { + get { return 0; } + } + + // Use reference equality because there's only one EmptySelection per text area. + public override int GetHashCode() + { + return RuntimeHelpers.GetHashCode(this); + } + + public override bool Equals(object obj) + { + return this == obj; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs new file mode 100644 index 000000000..fc775baf3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/IReadOnlySectionProvider.cs @@ -0,0 +1,32 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Determines whether the document can be modified. + /// + public interface IReadOnlySectionProvider + { + /// + /// Gets whether insertion is possible at the specified offset. + /// + bool CanInsert(int offset); + + /// + /// Gets the deletable segments inside the given segment. + /// + /// + /// All segments in the result must be within the given segment, and they must be returned in order + /// (e.g. if two segments are returned, EndOffset of first segment must be less than StartOffset of second segment). + /// + /// For replacements, the last segment being returned will be replaced with the new text. If an empty list is returned, + /// no replacement will be done. + /// + IEnumerable GetDeletableSegments(ISegment segment); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs new file mode 100644 index 000000000..bc2c6f5e4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeNativeWrapper.cs @@ -0,0 +1,206 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; +using Draw = System.Drawing; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Native API required for IME support. + /// + static class ImeNativeWrapper + { + [StructLayout(LayoutKind.Sequential)] + struct CompositionForm + { + public int dwStyle; + public POINT ptCurrentPos; + public RECT rcArea; + } + + [StructLayout(LayoutKind.Sequential)] + struct POINT + { + public int x; + public int y; + } + + [StructLayout(LayoutKind.Sequential)] + struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + struct LOGFONT + { + public int lfHeight; + public int lfWidth; + public int lfEscapement; + public int lfOrientation; + public int lfWeight; + public byte lfItalic; + public byte lfUnderline; + public byte lfStrikeOut; + public byte lfCharSet; + public byte lfOutPrecision; + public byte lfClipPrecision; + public byte lfQuality; + public byte lfPitchAndFamily; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string lfFaceName; + } + + const int CPS_CANCEL = 0x4; + const int NI_COMPOSITIONSTR = 0x15; + const int GCS_COMPSTR = 0x0008; + + public const int WM_IME_COMPOSITION = 0x10F; + public const int WM_IME_SETCONTEXT = 0x281; + public const int WM_INPUTLANGCHANGE = 0x51; + + [DllImport("imm32.dll")] + public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC); + [DllImport("imm32.dll")] + internal static extern IntPtr ImmGetContext(IntPtr hWnd); + [DllImport("imm32.dll")] + internal static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd); + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue = 0); + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref CompositionForm form); + [DllImport("imm32.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT font); + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool ImmGetCompositionFont(IntPtr hIMC, out LOGFONT font); + + [DllImport("msctf.dll")] + static extern int TF_CreateThreadMgr(out ITfThreadMgr threadMgr); + + [ThreadStatic] static bool textFrameworkThreadMgrInitialized; + [ThreadStatic] static ITfThreadMgr textFrameworkThreadMgr; + + public static ITfThreadMgr GetTextFrameworkThreadManager() + { + if (!textFrameworkThreadMgrInitialized) { + textFrameworkThreadMgrInitialized = true; + TF_CreateThreadMgr(out textFrameworkThreadMgr); + } + return textFrameworkThreadMgr; + } + + public static bool NotifyIme(IntPtr hIMC) + { + return ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL); + } + + public static bool SetCompositionWindow(HwndSource source, IntPtr hIMC, TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + Rect textViewBounds = textArea.TextView.GetBounds(source); + Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source); + CompositionForm form = new CompositionForm(); + form.dwStyle = 0x0020; + form.ptCurrentPos.x = (int)Math.Max(characterBounds.Left, textViewBounds.Left); + form.ptCurrentPos.y = (int)Math.Max(characterBounds.Top, textViewBounds.Top); + form.rcArea.left = (int)textViewBounds.Left; + form.rcArea.top = (int)textViewBounds.Top; + form.rcArea.right = (int)textViewBounds.Right; + form.rcArea.bottom = (int)textViewBounds.Bottom; + return ImmSetCompositionWindow(hIMC, ref form); + } + + public static bool SetCompositionFont(HwndSource source, IntPtr hIMC, TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + LOGFONT lf = new LOGFONT(); + Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source); + lf.lfFaceName = textArea.FontFamily.Source; + lf.lfHeight = (int)characterBounds.Height; + return ImmSetCompositionFont(hIMC, ref lf); + } + + static Rect GetBounds(this TextView textView, HwndSource source) + { + // this may happen during layout changes in AvalonDock, so we just return an empty rectangle + // in those cases. It should be refreshed immediately. + if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView)) + return EMPTY_RECT; + Rect displayRect = new Rect(0, 0, textView.ActualWidth, textView.ActualHeight); + return textView + .TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual + .TransformToDevice(source.RootVisual); // rect on HWND + } + + static readonly Rect EMPTY_RECT = new Rect(0, 0, 0, 0); + + static Rect GetCharacterBounds(this TextView textView, TextViewPosition pos, HwndSource source) + { + VisualLine vl = textView.GetVisualLine(pos.Line); + if (vl == null) + return EMPTY_RECT; + // this may happen during layout changes in AvalonDock, so we just return an empty rectangle + // in those cases. It should be refreshed immediately. + if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView)) + return EMPTY_RECT; + TextLine line = vl.GetTextLine(pos.VisualColumn); + Rect displayRect; + // calculate the display rect for the current character + if (pos.VisualColumn < vl.VisualLengthWithEndOfLineMarker) { + displayRect = line.GetTextBounds(pos.VisualColumn, 1).First().Rectangle; + displayRect.Offset(0, vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop)); + } else { + // if we are in virtual space, we just use one wide-space as character width + displayRect = new Rect(vl.GetVisualPosition(pos.VisualColumn, VisualYPosition.TextTop), + new Size(textView.WideSpaceWidth, textView.DefaultLineHeight)); + } + // adjust to current scrolling + displayRect.Offset(-textView.ScrollOffset); + return textView + .TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual + .TransformToDevice(source.RootVisual); // rect on HWND + } + } + + [ComImport, Guid("aa80e801-2021-11d2-93e0-0060b067b86e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface ITfThreadMgr + { + void Activate(out int clientId); + void Deactivate(); + void CreateDocumentMgr(out IntPtr docMgr); + void EnumDocumentMgrs(out IntPtr enumDocMgrs); + void GetFocus(out IntPtr docMgr); + void SetFocus(IntPtr docMgr); + void AssociateFocus(IntPtr hwnd, IntPtr newDocMgr, out IntPtr prevDocMgr); + void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus); + void GetFunctionProvider(ref Guid classId, out IntPtr funcProvider); + void EnumFunctionProviders(out IntPtr enumProviders); + void GetGlobalCompartment(out IntPtr compartmentMgr); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs new file mode 100644 index 000000000..ad4c22e10 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/ImeSupport.cs @@ -0,0 +1,150 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Editing +{ + class ImeSupport + { + readonly TextArea textArea; + IntPtr currentContext; + IntPtr previousContext; + IntPtr defaultImeWnd; + HwndSource hwndSource; + EventHandler requerySuggestedHandler; // we need to keep the event handler instance alive because CommandManager.RequerySuggested uses weak references + bool isReadOnly; + + public ImeSupport(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport); + // We listen to CommandManager.RequerySuggested for both caret offset changes and changes to the set of read-only sections. + // This is because there's no dedicated event for read-only section changes; but RequerySuggested needs to be raised anyways + // to invalidate the Paste command. + requerySuggestedHandler = OnRequerySuggested; + CommandManager.RequerySuggested += requerySuggestedHandler; + textArea.OptionChanged += TextAreaOptionChanged; + } + + void OnRequerySuggested(object sender, EventArgs e) + { + UpdateImeEnabled(); + } + + void TextAreaOptionChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "EnableImeSupport") { + InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport); + UpdateImeEnabled(); + } + } + + public void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + UpdateImeEnabled(); + } + + public void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + if (e.OldFocus == textArea && currentContext != IntPtr.Zero) + ImeNativeWrapper.NotifyIme(currentContext); + ClearContext(); + } + + void UpdateImeEnabled() + { + if (textArea.Options.EnableImeSupport && textArea.IsKeyboardFocused) { + bool newReadOnly = !textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset); + if (hwndSource == null || isReadOnly != newReadOnly) { + ClearContext(); // clear existing context (on read-only change) + isReadOnly = newReadOnly; + CreateContext(); + } + } else { + ClearContext(); + } + } + + void ClearContext() + { + if (hwndSource != null) { + ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, previousContext); + ImeNativeWrapper.ImmReleaseContext(defaultImeWnd, currentContext); + currentContext = IntPtr.Zero; + defaultImeWnd = IntPtr.Zero; + hwndSource.RemoveHook(WndProc); + hwndSource = null; + } + } + + void CreateContext() + { + hwndSource = (HwndSource)PresentationSource.FromVisual(this.textArea); + if (hwndSource != null) { + if (isReadOnly) { + defaultImeWnd = IntPtr.Zero; + currentContext = IntPtr.Zero; + } else { + defaultImeWnd = ImeNativeWrapper.ImmGetDefaultIMEWnd(IntPtr.Zero); + currentContext = ImeNativeWrapper.ImmGetContext(defaultImeWnd); + } + previousContext = ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, currentContext); + hwndSource.AddHook(WndProc); + // UpdateCompositionWindow() will be called by the caret becoming visible + + var threadMgr = ImeNativeWrapper.GetTextFrameworkThreadManager(); + if (threadMgr != null) { + // Even though the docu says passing null is invalid, this seems to help + // activating the IME on the default input context that is shared with WPF + threadMgr.SetFocus(IntPtr.Zero); + } + } + } + + IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + switch (msg) { + case ImeNativeWrapper.WM_INPUTLANGCHANGE: + // Don't mark the message as handled; other windows + // might want to handle it as well. + + // If we have a context, recreate it + if (hwndSource != null) { + ClearContext(); + CreateContext(); + } + break; + case ImeNativeWrapper.WM_IME_COMPOSITION: + UpdateCompositionWindow(); + break; + } + return IntPtr.Zero; + } + + public void UpdateCompositionWindow() + { + if (currentContext != IntPtr.Zero) { + ImeNativeWrapper.SetCompositionFont(hwndSource, currentContext, textArea); + ImeNativeWrapper.SetCompositionWindow(hwndSource, currentContext, textArea); + } + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs new file mode 100644 index 000000000..3da96b08c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/LineNumberMargin.cs @@ -0,0 +1,243 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Margin showing line numbers. + /// + public class LineNumberMargin : AbstractMargin, IWeakEventListener + { + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + public static readonly DependencyProperty ForegroundProperty = + DependencyProperty.Register("Foreground", typeof(Brush), typeof(LineNumberMargin), new PropertyMetadata(Brushes.Gray)); + + + + static LineNumberMargin() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(LineNumberMargin), + new FrameworkPropertyMetadata(typeof(LineNumberMargin))); + } + + TextArea textArea; + + Typeface typeface; + double emSize; + + /// + protected override Size MeasureOverride(Size availableSize) + { + typeface = this.CreateTypeface(); + emSize = (double)GetValue(TextBlock.FontSizeProperty); + + FormattedText text = TextFormatterFactory.CreateFormattedText( + this, + new string('9', maxLineNumberLength), + typeface, + emSize, + Foreground + ); + return new Size(text.Width, 0); + } + + /// + protected override void OnRender(DrawingContext drawingContext) + { + TextView textView = this.TextView; + Size renderSize = this.RenderSize; + if (textView != null && textView.VisualLinesValid) { + var foreground = Foreground; + foreach (VisualLine line in textView.VisualLines) { + int lineNumber = line.FirstDocumentLine.LineNumber; + FormattedText text = TextFormatterFactory.CreateFormattedText( + this, + lineNumber.ToString(CultureInfo.CurrentCulture), + typeface, emSize, foreground + ); + double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop); + drawingContext.DrawText(text, new Point((renderSize.Width / 2) - text.Width + 5, y - textView.VerticalOffset)); + } + } + } + + /// + protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) + { + if (oldTextView != null) { + oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged; + } + base.OnTextViewChanged(oldTextView, newTextView); + if (newTextView != null) { + newTextView.VisualLinesChanged += TextViewVisualLinesChanged; + + // find the text area belonging to the new text view + textArea = newTextView.Services.GetService(typeof(TextArea)) as TextArea; + } else { + textArea = null; + } + InvalidateVisual(); + } + + /// + protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument) + { + if (oldDocument != null) { + PropertyChangedEventManager.RemoveListener(oldDocument, this, "LineCount"); + } + base.OnDocumentChanged(oldDocument, newDocument); + if (newDocument != null) { + PropertyChangedEventManager.AddListener(newDocument, this, "LineCount"); + } + OnDocumentLineCountChanged(); + } + + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(PropertyChangedEventManager)) { + OnDocumentLineCountChanged(); + return true; + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + + int maxLineNumberLength = 1; + + void OnDocumentLineCountChanged() + { + int documentLineCount = Document != null ? Document.LineCount : 1; + int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length; + + // The margin looks too small when there is only one digit, so always reserve space for + // at least two digits + if (newLength < 2) + newLength = 2; + + if (newLength != maxLineNumberLength) { + maxLineNumberLength = newLength; + InvalidateMeasure(); + } + } + + void TextViewVisualLinesChanged(object sender, EventArgs e) + { + InvalidateVisual(); + } + + AnchorSegment selectionStart; + bool selecting; + + /// + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + if (!e.Handled && TextView != null && textArea != null) { + e.Handled = true; + textArea.Focus(); + + SimpleSegment currentSeg = GetTextLineSegment(e); + if (currentSeg == SimpleSegment.Invalid) + return; + textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length; + if (CaptureMouse()) { + selecting = true; + selectionStart = new AnchorSegment(Document, currentSeg.Offset, currentSeg.Length); + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) { + SimpleSelection simpleSelection = textArea.Selection as SimpleSelection; + if (simpleSelection != null) + selectionStart = new AnchorSegment(Document, simpleSelection.SurroundingSegment); + } + textArea.Selection = Selection.Create(textArea, selectionStart); + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) { + ExtendSelection(currentSeg); + } + } + } + } + + SimpleSegment GetTextLineSegment(MouseEventArgs e) + { + Point pos = e.GetPosition(TextView); + pos.X = 0; + pos.Y += TextView.VerticalOffset; + VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y); + if (vl == null) + return SimpleSegment.Invalid; + TextLine tl = vl.GetTextLineByVisualYPosition(pos.Y); + int visualStartColumn = vl.GetTextLineVisualStartColumn(tl); + int visualEndColumn = visualStartColumn + tl.Length; + int relStart = vl.FirstDocumentLine.Offset; + int startOffset = vl.GetRelativeOffset(visualStartColumn) + relStart; + int endOffset = vl.GetRelativeOffset(visualEndColumn) + relStart; + if (endOffset == vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length) + endOffset += vl.LastDocumentLine.DelimiterLength; + return new SimpleSegment(startOffset, endOffset - startOffset); + } + + void ExtendSelection(SimpleSegment currentSeg) + { + if (currentSeg.Offset < selectionStart.Offset) { + textArea.Caret.Offset = currentSeg.Offset; + textArea.Selection = Selection.Create(textArea, currentSeg.Offset, selectionStart.Offset + selectionStart.Length); + } else { + textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length; + textArea.Selection = Selection.Create(textArea, selectionStart.Offset, currentSeg.Offset + currentSeg.Length); + } + } + + /// + protected override void OnMouseMove(MouseEventArgs e) + { + if (selecting && textArea != null && TextView != null) { + e.Handled = true; + SimpleSegment currentSeg = GetTextLineSegment(e); + if (currentSeg == SimpleSegment.Invalid) + return; + ExtendSelection(currentSeg); + } + base.OnMouseMove(e); + } + + /// + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (selecting) { + selecting = false; + selectionStart = null; + ReleaseMouseCapture(); + e.Handled = true; + } + base.OnMouseLeftButtonUp(e); + } + + /// + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + // accept clicks even when clicking on the background + return new PointHitTestResult(this, hitTestParameters.HitPoint); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs new file mode 100644 index 000000000..35cd1dd1b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/NoReadOnlySections.cs @@ -0,0 +1,50 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Collections.Generic; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// that has no read-only sections; all text is editable. + /// + sealed class NoReadOnlySections : IReadOnlySectionProvider + { + public static readonly NoReadOnlySections Instance = new NoReadOnlySections(); + + public bool CanInsert(int offset) + { + return true; + } + + public IEnumerable GetDeletableSegments(ISegment segment) + { + if (segment == null) + throw new ArgumentNullException("segment"); + // the segment is always deletable + return ExtensionMethods.Sequence(segment); + } + } + + /// + /// that completely disables editing. + /// + sealed class ReadOnlyDocument : IReadOnlySectionProvider + { + public static readonly ReadOnlyDocument Instance = new ReadOnlyDocument(); + + public bool CanInsert(int offset) + { + return false; + } + + public IEnumerable GetDeletableSegments(ISegment segment) + { + return Enumerable.Empty(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs new file mode 100644 index 000000000..2d87d7934 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/RectangleSelection.cs @@ -0,0 +1,396 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Rectangular selection ("box selection"). + /// + public sealed class RectangleSelection : Selection + { + #region Commands + /// + /// Expands the selection left by one character, creating a rectangular selection. + /// Key gesture: Alt+Shift+Left + /// + public static readonly RoutedUICommand BoxSelectLeftByCharacter = Command("BoxSelectLeftByCharacter"); + + /// + /// Expands the selection right by one character, creating a rectangular selection. + /// Key gesture: Alt+Shift+Right + /// + public static readonly RoutedUICommand BoxSelectRightByCharacter = Command("BoxSelectRightByCharacter"); + + /// + /// Expands the selection left by one word, creating a rectangular selection. + /// Key gesture: Ctrl+Alt+Shift+Left + /// + public static readonly RoutedUICommand BoxSelectLeftByWord = Command("BoxSelectLeftByWord"); + + /// + /// Expands the selection left by one word, creating a rectangular selection. + /// Key gesture: Ctrl+Alt+Shift+Right + /// + public static readonly RoutedUICommand BoxSelectRightByWord = Command("BoxSelectRightByWord"); + + /// + /// Expands the selection up by one line, creating a rectangular selection. + /// Key gesture: Alt+Shift+Up + /// + public static readonly RoutedUICommand BoxSelectUpByLine = Command("BoxSelectUpByLine"); + + /// + /// Expands the selection up by one line, creating a rectangular selection. + /// Key gesture: Alt+Shift+Down + /// + public static readonly RoutedUICommand BoxSelectDownByLine = Command("BoxSelectDownByLine"); + + /// + /// Expands the selection to the start of the line, creating a rectangular selection. + /// Key gesture: Alt+Shift+Home + /// + public static readonly RoutedUICommand BoxSelectToLineStart = Command("BoxSelectToLineStart"); + + /// + /// Expands the selection to the end of the line, creating a rectangular selection. + /// Key gesture: Alt+Shift+End + /// + public static readonly RoutedUICommand BoxSelectToLineEnd = Command("BoxSelectToLineEnd"); + + static RoutedUICommand Command(string name) + { + return new RoutedUICommand(name, name, typeof(RectangleSelection)); + } + #endregion + + TextDocument document; + readonly int startLine, endLine; + readonly double startXPos, endXPos; + readonly int topLeftOffset, bottomRightOffset; + readonly TextViewPosition start, end; + + readonly List segments = new List(); + + #region Constructors + /// + /// Creates a new rectangular selection. + /// + public RectangleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end) + : base(textArea) + { + InitDocument(); + this.startLine = start.Line; + this.endLine = end.Line; + this.startXPos = GetXPos(textArea, start); + this.endXPos = GetXPos(textArea, end); + CalculateSegments(); + this.topLeftOffset = this.segments.First().StartOffset; + this.bottomRightOffset = this.segments.Last().EndOffset; + + this.start = start; + this.end = end; + } + + private RectangleSelection(TextArea textArea, int startLine, double startXPos, TextViewPosition end) + : base(textArea) + { + InitDocument(); + this.startLine = startLine; + this.endLine = end.Line; + this.startXPos = startXPos; + this.endXPos = GetXPos(textArea, end); + CalculateSegments(); + this.topLeftOffset = this.segments.First().StartOffset; + this.bottomRightOffset = this.segments.Last().EndOffset; + + this.start = GetStart(); + this.end = end; + } + + private RectangleSelection(TextArea textArea, TextViewPosition start, int endLine, double endXPos) + : base(textArea) + { + InitDocument(); + this.startLine = start.Line; + this.endLine = endLine; + this.startXPos = GetXPos(textArea, start); + this.endXPos = endXPos; + CalculateSegments(); + this.topLeftOffset = this.segments.First().StartOffset; + this.bottomRightOffset = this.segments.Last().EndOffset; + + this.start = start; + this.end = GetEnd(); + } + + void InitDocument() + { + document = textArea.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + } + + static double GetXPos(TextArea textArea, TextViewPosition pos) + { + DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line); + VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine); + int vc = visualLine.ValidateVisualColumn(pos, true); + TextLine textLine = visualLine.GetTextLine(vc); + return visualLine.GetTextLineVisualXPosition(textLine, vc); + } + + void CalculateSegments() + { + DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine)); + do { + VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine); + int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true); + int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true); + + int baseOffset = vl.FirstDocumentLine.Offset; + int startOffset = baseOffset + vl.GetRelativeOffset(startVC); + int endOffset = baseOffset + vl.GetRelativeOffset(endVC); + segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC)); + + nextLine = vl.LastDocumentLine.NextLine; + } while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine)); + } + + TextViewPosition GetStart() + { + SelectionSegment segment = (startLine < endLine ? segments.First() : segments.Last()); + if (startXPos < endXPos) { + return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn); + } else { + return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn); + } + } + + TextViewPosition GetEnd() + { + SelectionSegment segment = (startLine < endLine ? segments.Last() : segments.First()); + if (startXPos < endXPos) { + return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn); + } else { + return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn); + } + } + #endregion + + /// + public override string GetText() + { + StringBuilder b = new StringBuilder(); + foreach (ISegment s in this.Segments) { + if (b.Length > 0) + b.AppendLine(); + b.Append(document.GetText(s)); + } + return b.ToString(); + } + + /// + public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition) + { + return SetEndpoint(endPosition); + } + + /// + public override int Length { + get { + return this.Segments.Sum(s => s.Length); + } + } + + /// + public override bool EnableVirtualSpace { + get { return true; } + } + + /// + public override ISegment SurroundingSegment { + get { + return new SimpleSegment(topLeftOffset, bottomRightOffset - topLeftOffset); + } + } + + /// + public override IEnumerable Segments { + get { return segments; } + } + + /// + public override TextViewPosition StartPosition { + get { return start; } + } + + /// + public override TextViewPosition EndPosition { + get { return end; } + } + + /// + public override bool Equals(object obj) + { + RectangleSelection r = obj as RectangleSelection; + return r != null && r.textArea == this.textArea + && r.topLeftOffset == this.topLeftOffset && r.bottomRightOffset == this.bottomRightOffset + && r.startLine == this.startLine && r.endLine == this.endLine + && r.startXPos == this.startXPos && r.endXPos == this.endXPos; + } + + /// + public override int GetHashCode() + { + return topLeftOffset ^ bottomRightOffset; + } + + /// + public override Selection SetEndpoint(TextViewPosition endPosition) + { + return new RectangleSelection(textArea, startLine, startXPos, endPosition); + } + + int GetVisualColumnFromXPos(int line, double xPos) + { + var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line)); + return vl.GetVisualColumn(new Point(xPos, 0), true); + } + + /// + public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e) + { + TextLocation newStartLocation = textArea.Document.GetLocation(e.GetNewOffset(topLeftOffset, AnchorMovementType.AfterInsertion)); + TextLocation newEndLocation = textArea.Document.GetLocation(e.GetNewOffset(bottomRightOffset, AnchorMovementType.BeforeInsertion)); + + return new RectangleSelection(textArea, + new TextViewPosition(newStartLocation, GetVisualColumnFromXPos(newStartLocation.Line, startXPos)), + new TextViewPosition(newEndLocation, GetVisualColumnFromXPos(newEndLocation.Line, endXPos))); + } + + /// + public override void ReplaceSelectionWithText(string newText) + { + if (newText == null) + throw new ArgumentNullException("newText"); + using (textArea.Document.RunUpdate()) { + TextViewPosition start = new TextViewPosition(document.GetLocation(topLeftOffset), GetVisualColumnFromXPos(startLine, startXPos)); + TextViewPosition end = new TextViewPosition(document.GetLocation(bottomRightOffset), GetVisualColumnFromXPos(endLine, endXPos)); + int insertionLength; + int totalInsertionLength = 0; + int firstInsertionLength = 0; + int editOffset = Math.Min(topLeftOffset, bottomRightOffset); + TextViewPosition pos; + if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) { + // insert same text into every line + foreach (SelectionSegment lineSegment in this.Segments.Reverse()) { + ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength); + totalInsertionLength += insertionLength; + firstInsertionLength = insertionLength; + } + + int newEndOffset = editOffset + totalInsertionLength; + pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength)); + + textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos)); + } else { + string[] lines = newText.Split(NewLineFinder.NewlineStrings, segments.Count, StringSplitOptions.None); + int line = Math.Min(startLine, endLine); + for (int i = lines.Length - 1; i >= 0; i--) { + ReplaceSingleLineText(textArea, segments[i], lines[i], out insertionLength); + firstInsertionLength = insertionLength; + } + pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength)); + textArea.ClearSelection(); + } + textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault(); + } + } + + void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength) + { + if (lineSegment.Length == 0) { + if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) { + newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn)); + textArea.Document.Insert(lineSegment.StartOffset, newText); + } + } else { + ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment); + for (int i = segmentsToDelete.Length - 1; i >= 0; i--) { + if (i == segmentsToDelete.Length - 1) { + if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) { + newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn)); + } + textArea.Document.Replace(segmentsToDelete[i], newText); + } else { + textArea.Document.Remove(segmentsToDelete[i]); + } + } + } + insertionLength = newText.Length; + } + + /// + /// Performs a rectangular paste operation. + /// + public static bool PerformRectangularPaste(TextArea textArea, TextViewPosition startPosition, string text, bool selectInsertedText) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + if (text == null) + throw new ArgumentNullException("text"); + int newLineCount = text.Count(c => c == '\n'); // TODO might not work in all cases, but single \r line endings are really rare today. + TextLocation endLocation = new TextLocation(startPosition.Line + newLineCount, startPosition.Column); + if (endLocation.Line <= textArea.Document.LineCount) { + int endOffset = textArea.Document.GetOffset(endLocation); + if (textArea.Selection.EnableVirtualSpace || textArea.Document.GetLocation(endOffset) == endLocation) { + RectangleSelection rsel = new RectangleSelection(textArea, startPosition, endLocation.Line, GetXPos(textArea, startPosition)); + rsel.ReplaceSelectionWithText(text); + if (selectInsertedText && textArea.Selection is RectangleSelection) { + RectangleSelection sel = (RectangleSelection)textArea.Selection; + textArea.Selection = new RectangleSelection(textArea, startPosition, sel.endLine, sel.endXPos); + } + return true; + } + } + return false; + } + + /// + /// Gets the name of the entry in the DataObject that signals rectangle selections. + /// + public const string RectangularSelectionDataType = "AvalonEditRectangularSelection"; + + /// + public override System.Windows.DataObject CreateDataObject(TextArea textArea) + { + var data = base.CreateDataObject(textArea); + + MemoryStream isRectangle = new MemoryStream(1); + isRectangle.WriteByte(1); + data.SetData(RectangularSelectionDataType, isRectangle, false); + return data; + } + + /// + public override string ToString() + { + // It's possible that ToString() gets called on old (invalid) selections, e.g. for "change from... to..." debug message + // make sure we don't crash even when the desired locations don't exist anymore. + return string.Format("[RectangleSelection {0} {1} {2} to {3} {4} {5}]", startLine, topLeftOffset, startXPos, endLine, bottomRightOffset, endXPos); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs new file mode 100644 index 000000000..014fa8680 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Selection.cs @@ -0,0 +1,268 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Highlighting; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Base class for selections. + /// + public abstract class Selection + { + /// + /// Creates a new simple selection that selects the text from startOffset to endOffset. + /// + public static Selection Create(TextArea textArea, int startOffset, int endOffset) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + if (startOffset == endOffset) + return textArea.emptySelection; + else + return new SimpleSelection(textArea, + new TextViewPosition(textArea.Document.GetLocation(startOffset)), + new TextViewPosition(textArea.Document.GetLocation(endOffset))); + } + + internal static Selection Create(TextArea textArea, TextViewPosition start, TextViewPosition end) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + if (textArea.Document.GetOffset(start.Location) == textArea.Document.GetOffset(end.Location) && start.VisualColumn == end.VisualColumn) + return textArea.emptySelection; + else + return new SimpleSelection(textArea, start, end); + } + + /// + /// Creates a new simple selection that selects the text in the specified segment. + /// + public static Selection Create(TextArea textArea, ISegment segment) + { + if (segment == null) + throw new ArgumentNullException("segment"); + return Create(textArea, segment.Offset, segment.EndOffset); + } + + internal readonly TextArea textArea; + + /// + /// Constructor for Selection. + /// + protected Selection(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + } + + /// + /// Gets the start position of the selection. + /// + public abstract TextViewPosition StartPosition { get; } + + /// + /// Gets the end position of the selection. + /// + public abstract TextViewPosition EndPosition { get; } + + /// + /// Gets the selected text segments. + /// + public abstract IEnumerable Segments { get; } + + /// + /// Gets the smallest segment that contains all segments in this selection. + /// May return null if the selection is empty. + /// + public abstract ISegment SurroundingSegment { get; } + + /// + /// Replaces the selection with the specified text. + /// + public abstract void ReplaceSelectionWithText(string newText); + + internal string AddSpacesIfRequired(string newText, TextViewPosition start, TextViewPosition end) + { + if (EnableVirtualSpace && InsertVirtualSpaces(newText, start, end)) { + var line = textArea.Document.GetLineByNumber(start.Line); + string lineText = textArea.Document.GetText(line); + var vLine = textArea.TextView.GetOrConstructVisualLine(line); + int colDiff = start.VisualColumn - vLine.VisualLengthWithEndOfLineMarker; + if (colDiff > 0) { + string additionalSpaces = ""; + if (!textArea.Options.ConvertTabsToSpaces && lineText.Trim('\t').Length == 0) { + int tabCount = (int)(colDiff / textArea.Options.IndentationSize); + additionalSpaces = new string('\t', tabCount); + colDiff -= tabCount * textArea.Options.IndentationSize; + } + additionalSpaces += new string(' ', colDiff); + return additionalSpaces + newText; + } + } + return newText; + } + + bool InsertVirtualSpaces(string newText, TextViewPosition start, TextViewPosition end) + { + return (!string.IsNullOrEmpty(newText) || !(IsInVirtualSpace(start) && IsInVirtualSpace(end))) + && newText != "\r\n" + && newText != "\n" + && newText != "\r"; + } + + bool IsInVirtualSpace(TextViewPosition pos) + { + return pos.VisualColumn > textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(pos.Line)).VisualLength; + } + + /// + /// Updates the selection when the document changes. + /// + public abstract Selection UpdateOnDocumentChange(DocumentChangeEventArgs e); + + /// + /// Gets whether the selection is empty. + /// + public virtual bool IsEmpty { + get { return Length == 0; } + } + + /// + /// Gets whether virtual space is enabled for this selection. + /// + public virtual bool EnableVirtualSpace { + get { return textArea.Options.EnableVirtualSpace; } + } + + /// + /// Gets the selection length. + /// + public abstract int Length { get; } + + /// + /// Returns a new selection with the changed end point. + /// + /// Cannot set endpoint for empty selection + public abstract Selection SetEndpoint(TextViewPosition endPosition); + + /// + /// If this selection is empty, starts a new selection from to + /// , otherwise, changes the endpoint of this selection. + /// + public abstract Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition); + + /// + /// Gets whether the selection is multi-line. + /// + public virtual bool IsMultiline { + get { + ISegment surroundingSegment = this.SurroundingSegment; + if (surroundingSegment == null) + return false; + int start = surroundingSegment.Offset; + int end = start + surroundingSegment.Length; + var document = textArea.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + return document.GetLineByOffset(start) != document.GetLineByOffset(end); + } + } + + /// + /// Gets the selected text. + /// + public virtual string GetText() + { + var document = textArea.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + StringBuilder b = null; + string text = null; + foreach (ISegment s in Segments) { + if (text != null) { + if (b == null) + b = new StringBuilder(text); + else + b.Append(text); + } + text = document.GetText(s); + } + if (b != null) { + if (text != null) b.Append(text); + return b.ToString(); + } else { + return text ?? string.Empty; + } + } + + /// + /// Creates a HTML fragment for the selected text. + /// + public string CreateHtmlFragment(HtmlOptions options) + { + if (options == null) + throw new ArgumentNullException("options"); + IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter; + StringBuilder html = new StringBuilder(); + bool first = true; + foreach (ISegment selectedSegment in this.Segments) { + if (first) + first = false; + else + html.AppendLine("
"); + html.Append(HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, selectedSegment, options)); + } + return html.ToString(); + } + + /// + public abstract override bool Equals(object obj); + + /// + public abstract override int GetHashCode(); + + /// + /// Gets whether the specified offset is included in the selection. + /// + /// True, if the selection contains the offset (selection borders inclusive); + /// otherwise, false. + public virtual bool Contains(int offset) + { + if (this.IsEmpty) + return false; + if (this.SurroundingSegment.Contains(offset)) { + foreach (ISegment s in this.Segments) { + if (s.Contains(offset)) { + return true; + } + } + } + return false; + } + + /// + /// Creates a data object containing the selection's text. + /// + public virtual DataObject CreateDataObject(TextArea textArea) + { + string text = GetText(); + // Ensure we use the appropriate newline sequence for the OS + DataObject data = new DataObject(TextUtilities.NormalizeNewLines(text, Environment.NewLine)); + // we cannot use DataObject.SetText - then we cannot drag to SciTe + // (but dragging to Word works in both cases) + + // Also copy text in HTML format to clipboard - good for pasting text into Word + // or to the SharpDevelop forums. + HtmlClipboard.SetHtml(data, CreateHtmlFragment(new HtmlOptions(textArea.Options))); + return data; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs new file mode 100644 index 000000000..857c4c060 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionColorizer.cs @@ -0,0 +1,58 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Editing +{ + sealed class SelectionColorizer : ColorizingTransformer + { + TextArea textArea; + + public SelectionColorizer(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + } + + protected override void Colorize(ITextRunConstructionContext context) + { + // if SelectionForeground is null, keep the existing foreground color + if (textArea.SelectionForeground == null) + return; + + int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset; + int lineEndOffset = context.VisualLine.LastDocumentLine.Offset + context.VisualLine.LastDocumentLine.TotalLength; + + foreach (SelectionSegment segment in textArea.Selection.Segments) { + int segmentStart = segment.StartOffset; + int segmentEnd = segment.EndOffset; + if (segmentEnd <= lineStartOffset) + continue; + if (segmentStart >= lineEndOffset) + continue; + int startColumn; + if (segmentStart < lineStartOffset) + startColumn = 0; + else + startColumn = context.VisualLine.ValidateVisualColumn(segment.StartOffset, segment.StartVisualColumn, textArea.Selection.EnableVirtualSpace); + + int endColumn; + if (segmentEnd > lineEndOffset) + endColumn = textArea.Selection.EnableVirtualSpace ? int.MaxValue : context.VisualLine.VisualLengthWithEndOfLineMarker; + else + endColumn = context.VisualLine.ValidateVisualColumn(segment.EndOffset, segment.EndVisualColumn, textArea.Selection.EnableVirtualSpace); + + ChangeVisualElements( + startColumn, endColumn, + element => { + element.TextRunProperties.SetForegroundBrush(textArea.SelectionForeground); + }); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs new file mode 100644 index 000000000..28631affe --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionLayer.cs @@ -0,0 +1,53 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media; + +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Editing +{ + sealed class SelectionLayer : Layer, IWeakEventListener + { + readonly TextArea textArea; + + public SelectionLayer(TextArea textArea) : base(textArea.TextView, KnownLayer.Selection) + { + this.IsHitTestVisible = false; + + this.textArea = textArea; + TextViewWeakEventManager.VisualLinesChanged.AddListener(textView, this); + TextViewWeakEventManager.ScrollOffsetChanged.AddListener(textView, this); + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextViewWeakEventManager.VisualLinesChanged) + || managerType == typeof(TextViewWeakEventManager.ScrollOffsetChanged)) + { + InvalidateVisual(); + return true; + } + return false; + } + + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToMiddleOfPixels = true; + geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Selection.EnableVirtualSpace; + geoBuilder.CornerRadius = textArea.SelectionCornerRadius; + foreach (var segment in textArea.Selection.Segments) { + geoBuilder.AddSegment(textView, segment); + } + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(textArea.SelectionBrush, textArea.SelectionBorder, geometry); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs new file mode 100644 index 000000000..3c5ec51dc --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionMouseHandler.cs @@ -0,0 +1,631 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media.TextFormatting; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Handles selection of text using the mouse. + /// + sealed class SelectionMouseHandler : ITextAreaInputHandler + { + #region enum SelectionMode + enum SelectionMode + { + /// + /// no selection (no mouse button down) + /// + None, + /// + /// left mouse button down on selection, might be normal click + /// or might be drag'n'drop + /// + PossibleDragStart, + /// + /// dragging text + /// + Drag, + /// + /// normal selection (click+drag) + /// + Normal, + /// + /// whole-word selection (double click+drag or ctrl+click+drag) + /// + WholeWord, + /// + /// whole-line selection (triple click+drag) + /// + WholeLine, + /// + /// rectangular selection (alt+click+drag) + /// + Rectangular + } + #endregion + + readonly TextArea textArea; + + SelectionMode mode; + AnchorSegment startWord; + Point possibleDragStartMousePos; + + #region Constructor + Attach + Detach + public SelectionMouseHandler(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + } + + public TextArea TextArea { + get { return textArea; } + } + + public void Attach() + { + textArea.MouseLeftButtonDown += textArea_MouseLeftButtonDown; + textArea.MouseMove += textArea_MouseMove; + textArea.MouseLeftButtonUp += textArea_MouseLeftButtonUp; + textArea.QueryCursor += textArea_QueryCursor; + textArea.OptionChanged += textArea_OptionChanged; + + enableTextDragDrop = textArea.Options.EnableTextDragDrop; + if (enableTextDragDrop) { + AttachDragDrop(); + } + } + + public void Detach() + { + mode = SelectionMode.None; + textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown; + textArea.MouseMove -= textArea_MouseMove; + textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp; + textArea.QueryCursor -= textArea_QueryCursor; + textArea.OptionChanged -= textArea_OptionChanged; + if (enableTextDragDrop) { + DetachDragDrop(); + } + } + + void AttachDragDrop() + { + textArea.AllowDrop = true; + textArea.GiveFeedback += textArea_GiveFeedback; + textArea.QueryContinueDrag += textArea_QueryContinueDrag; + textArea.DragEnter += textArea_DragEnter; + textArea.DragOver += textArea_DragOver; + textArea.DragLeave += textArea_DragLeave; + textArea.Drop += textArea_Drop; + } + + void DetachDragDrop() + { + textArea.AllowDrop = false; + textArea.GiveFeedback -= textArea_GiveFeedback; + textArea.QueryContinueDrag -= textArea_QueryContinueDrag; + textArea.DragEnter -= textArea_DragEnter; + textArea.DragOver -= textArea_DragOver; + textArea.DragLeave -= textArea_DragLeave; + textArea.Drop -= textArea_Drop; + } + + bool enableTextDragDrop; + + void textArea_OptionChanged(object sender, PropertyChangedEventArgs e) + { + bool newEnableTextDragDrop = textArea.Options.EnableTextDragDrop; + if (newEnableTextDragDrop != enableTextDragDrop) { + enableTextDragDrop = newEnableTextDragDrop; + if (newEnableTextDragDrop) + AttachDragDrop(); + else + DetachDragDrop(); + } + } + #endregion + + #region Dropping text + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_DragEnter(object sender, DragEventArgs e) + { + try { + e.Effects = GetEffect(e); + textArea.Caret.Show(); + } catch (Exception ex) { + OnDragException(ex); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_DragOver(object sender, DragEventArgs e) + { + try { + e.Effects = GetEffect(e); + } catch (Exception ex) { + OnDragException(ex); + } + } + + DragDropEffects GetEffect(DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) { + e.Handled = true; + int visualColumn; + int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn); + if (offset >= 0) { + textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn); + textArea.Caret.DesiredXPos = double.NaN; + if (textArea.ReadOnlySectionProvider.CanInsert(offset)) { + if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move + && (e.KeyStates & DragDropKeyStates.ControlKey) != DragDropKeyStates.ControlKey) + { + return DragDropEffects.Move; + } else { + return e.AllowedEffects & DragDropEffects.Copy; + } + } + } + } + return DragDropEffects.None; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_DragLeave(object sender, DragEventArgs e) + { + try { + e.Handled = true; + if (!textArea.IsKeyboardFocusWithin) + textArea.Caret.Hide(); + } catch (Exception ex) { + OnDragException(ex); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_Drop(object sender, DragEventArgs e) + { + try { + DragDropEffects effect = GetEffect(e); + e.Effects = effect; + if (effect != DragDropEffects.None) { + string text = e.Data.GetData(DataFormats.UnicodeText, true) as string; + if (text != null) { + int start = textArea.Caret.Offset; + if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) { + Debug.WriteLine("Drop: did not drop: drop target is inside selection"); + e.Effects = DragDropEffects.None; + } else { + Debug.WriteLine("Drop: insert at " + start); + + bool rectangular = e.Data.GetDataPresent(RectangleSelection.RectangularSelectionDataType); + + string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line); + text = TextUtilities.NormalizeNewLines(text, newLine); + + // Mark the undo group with the currentDragDescriptor, if the drag + // is originating from the same control. This allows combining + // the undo groups when text is moved. + textArea.Document.UndoStack.StartUndoGroup(this.currentDragDescriptor); + try { + if (rectangular && RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, true)) { + + } else { + textArea.Document.Insert(start, text); + textArea.Selection = Selection.Create(textArea, start, start + text.Length); + } + } finally { + textArea.Document.UndoStack.EndUndoGroup(); + } + } + e.Handled = true; + } + } + } catch (Exception ex) { + OnDragException(ex); + } + } + + void OnDragException(Exception ex) + { + // WPF swallows exceptions during drag'n'drop or reports them incorrectly, so + // we re-throw them later to allow the application's unhandled exception handler + // to catch them + textArea.Dispatcher.BeginInvoke( + DispatcherPriority.Normal, + new Action(delegate { + throw new DragDropException("Exception during drag'n'drop", ex); + })); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e) + { + try { + e.UseDefaultCursors = true; + e.Handled = true; + } catch (Exception ex) { + OnDragException(ex); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) + { + try { + if (e.EscapePressed) { + e.Action = DragAction.Cancel; + } else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) { + e.Action = DragAction.Drop; + } else { + e.Action = DragAction.Continue; + } + e.Handled = true; + } catch (Exception ex) { + OnDragException(ex); + } + } + #endregion + + #region Start Drag + object currentDragDescriptor; + + void StartDrag() + { + // prevent nested StartDrag calls + mode = SelectionMode.Drag; + + // mouse capture and Drag'n'Drop doesn't mix + textArea.ReleaseMouseCapture(); + + DataObject dataObject = textArea.Selection.CreateDataObject(textArea); + + DragDropEffects allowedEffects = DragDropEffects.All; + var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList(); + foreach (ISegment s in deleteOnMove) { + ISegment[] result = textArea.GetDeletableSegments(s); + if (result.Length != 1 || result[0].Offset != s.Offset || result[0].EndOffset != s.EndOffset) { + allowedEffects &= ~DragDropEffects.Move; + } + } + + object dragDescriptor = new object(); + this.currentDragDescriptor = dragDescriptor; + + DragDropEffects resultEffect; + using (textArea.AllowCaretOutsideSelection()) { + var oldCaretPosition = textArea.Caret.Position; + try { + Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects); + resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects); + Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect); + } catch (COMException ex) { + // ignore COM errors - don't crash on badly implemented drop targets + Debug.WriteLine("DoDragDrop failed: " + ex.ToString()); + return; + } + if (resultEffect == DragDropEffects.None) { + // reset caret if drag was aborted + textArea.Caret.Position = oldCaretPosition; + } + } + + this.currentDragDescriptor = null; + + if (deleteOnMove != null && resultEffect == DragDropEffects.Move && (allowedEffects & DragDropEffects.Move) == DragDropEffects.Move) { + bool draggedInsideSingleDocument = (dragDescriptor == textArea.Document.UndoStack.LastGroupDescriptor); + if (draggedInsideSingleDocument) + textArea.Document.UndoStack.StartContinuedUndoGroup(null); + textArea.Document.BeginUpdate(); + try { + foreach (ISegment s in deleteOnMove) { + textArea.Document.Remove(s.Offset, s.Length); + } + } finally { + textArea.Document.EndUpdate(); + if (draggedInsideSingleDocument) + textArea.Document.UndoStack.EndUndoGroup(); + } + } + } + #endregion + + #region QueryCursor + // provide the IBeam Cursor for the text area + void textArea_QueryCursor(object sender, QueryCursorEventArgs e) + { + if (!e.Handled) { + if (mode != SelectionMode.None || !enableTextDragDrop) { + e.Cursor = Cursors.IBeam; + e.Handled = true; + } else if (textArea.TextView.VisualLinesValid) { + // Only query the cursor if the visual lines are valid. + // If they are invalid, the cursor will get re-queried when the visual lines + // get refreshed. + Point p = e.GetPosition(textArea.TextView); + if (p.X >= 0 && p.Y >= 0 && p.X <= textArea.TextView.ActualWidth && p.Y <= textArea.TextView.ActualHeight) { + int visualColumn; + int offset = GetOffsetFromMousePosition(e, out visualColumn); + if (textArea.Selection.Contains(offset)) + e.Cursor = Cursors.Arrow; + else + e.Cursor = Cursors.IBeam; + e.Handled = true; + } + } + } + } + #endregion + + #region LeftButtonDown + void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + mode = SelectionMode.None; + if (!e.Handled && e.ChangedButton == MouseButton.Left) { + ModifierKeys modifiers = Keyboard.Modifiers; + bool shift = (modifiers & ModifierKeys.Shift) == ModifierKeys.Shift; + if (enableTextDragDrop && e.ClickCount == 1 && !shift) { + int visualColumn; + int offset = GetOffsetFromMousePosition(e, out visualColumn); + if (textArea.Selection.Contains(offset)) { + if (textArea.CaptureMouse()) { + mode = SelectionMode.PossibleDragStart; + possibleDragStartMousePos = e.GetPosition(textArea); + } + e.Handled = true; + return; + } + } + + var oldPosition = textArea.Caret.Position; + SetCaretOffsetToMousePosition(e); + + + if (!shift) { + textArea.ClearSelection(); + } + if (textArea.CaptureMouse()) { + if ((modifiers & ModifierKeys.Alt) == ModifierKeys.Alt && textArea.Options.EnableRectangularSelection) { + mode = SelectionMode.Rectangular; + if (shift && textArea.Selection is RectangleSelection) { + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + } + } else if (e.ClickCount == 1 && ((modifiers & ModifierKeys.Control) == 0)) { + mode = SelectionMode.Normal; + if (shift && !(textArea.Selection is RectangleSelection)) { + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + } + } else { + SimpleSegment startWord; + if (e.ClickCount == 3) { + mode = SelectionMode.WholeLine; + startWord = GetLineAtMousePosition(e); + } else { + mode = SelectionMode.WholeWord; + startWord = GetWordAtMousePosition(e); + } + if (startWord == SimpleSegment.Invalid) { + mode = SelectionMode.None; + textArea.ReleaseMouseCapture(); + return; + } + if (shift && !textArea.Selection.IsEmpty) { + if (startWord.Offset < textArea.Selection.SurroundingSegment.Offset) { + textArea.Selection = textArea.Selection.SetEndpoint(new TextViewPosition(textArea.Document.GetLocation(startWord.Offset))); + } else if (startWord.EndOffset > textArea.Selection.SurroundingSegment.EndOffset) { + textArea.Selection = textArea.Selection.SetEndpoint(new TextViewPosition(textArea.Document.GetLocation(startWord.EndOffset))); + } + this.startWord = new AnchorSegment(textArea.Document, textArea.Selection.SurroundingSegment); + } else { + textArea.Selection = Selection.Create(textArea, startWord.Offset, startWord.EndOffset); + this.startWord = new AnchorSegment(textArea.Document, startWord.Offset, startWord.Length); + } + } + } + } + e.Handled = true; + } + #endregion + + #region Mouse Position <-> Text coordinates + SimpleSegment GetWordAtMousePosition(MouseEventArgs e) + { + TextView textView = textArea.TextView; + if (textView == null) return SimpleSegment.Invalid; + Point pos = e.GetPosition(textView); + if (pos.Y < 0) + pos.Y = 0; + if (pos.Y > textView.ActualHeight) + pos.Y = textView.ActualHeight; + pos += textView.ScrollOffset; + VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); + if (line != null) { + int visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace); + int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol, textArea.Selection.EnableVirtualSpace); + if (wordStartVC == -1) + wordStartVC = 0; + int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol, textArea.Selection.EnableVirtualSpace); + if (wordEndVC == -1) + wordEndVC = line.VisualLength; + int relOffset = line.FirstDocumentLine.Offset; + int wordStartOffset = line.GetRelativeOffset(wordStartVC) + relOffset; + int wordEndOffset = line.GetRelativeOffset(wordEndVC) + relOffset; + return new SimpleSegment(wordStartOffset, wordEndOffset - wordStartOffset); + } else { + return SimpleSegment.Invalid; + } + } + + SimpleSegment GetLineAtMousePosition(MouseEventArgs e) + { + TextView textView = textArea.TextView; + if (textView == null) return SimpleSegment.Invalid; + Point pos = e.GetPosition(textView); + if (pos.Y < 0) + pos.Y = 0; + if (pos.Y > textView.ActualHeight) + pos.Y = textView.ActualHeight; + pos += textView.ScrollOffset; + VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); + if (line != null) { + return new SimpleSegment(line.StartOffset, line.LastDocumentLine.EndOffset - line.StartOffset); + } else { + return SimpleSegment.Invalid; + } + } + + int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn) + { + return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn); + } + + int GetOffsetFromMousePosition(Point positionRelativeToTextView, out int visualColumn) + { + visualColumn = 0; + TextView textView = textArea.TextView; + Point pos = positionRelativeToTextView; + if (pos.Y < 0) + pos.Y = 0; + if (pos.Y > textView.ActualHeight) + pos.Y = textView.ActualHeight; + pos += textView.ScrollOffset; + if (pos.Y > textView.DocumentHeight) + pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon; + VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); + if (line != null) { + visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace); + return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; + } + return -1; + } + + int GetOffsetFromMousePositionFirstTextLineOnly(Point positionRelativeToTextView, out int visualColumn) + { + visualColumn = 0; + TextView textView = textArea.TextView; + Point pos = positionRelativeToTextView; + if (pos.Y < 0) + pos.Y = 0; + if (pos.Y > textView.ActualHeight) + pos.Y = textView.ActualHeight; + pos += textView.ScrollOffset; + if (pos.Y > textView.DocumentHeight) + pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon; + VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); + if (line != null) { + visualColumn = line.GetVisualColumn(line.TextLines.First(), pos.X, textArea.Selection.EnableVirtualSpace); + return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; + } + return -1; + } + #endregion + + #region MouseMove + void textArea_MouseMove(object sender, MouseEventArgs e) + { + if (e.Handled) + return; + if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) { + e.Handled = true; + if (textArea.TextView.VisualLinesValid) { + // If the visual lines are not valid, don't extend the selection. + // Extending the selection forces a VisualLine refresh, and it is sufficient + // to do that on MouseUp, we don't have to do it every MouseMove. + ExtendSelectionToMouse(e); + } + } else if (mode == SelectionMode.PossibleDragStart) { + e.Handled = true; + Vector mouseMovement = e.GetPosition(textArea) - possibleDragStartMousePos; + if (Math.Abs(mouseMovement.X) > SystemParameters.MinimumHorizontalDragDistance + || Math.Abs(mouseMovement.Y) > SystemParameters.MinimumVerticalDragDistance) + { + StartDrag(); + } + } + } + #endregion + + #region ExtendSelection + void SetCaretOffsetToMousePosition(MouseEventArgs e) + { + SetCaretOffsetToMousePosition(e, null); + } + + void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment) + { + int visualColumn; + int offset; + if (mode == SelectionMode.Rectangular) + offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn); + else + offset = GetOffsetFromMousePosition(e, out visualColumn); + if (allowedSegment != null) { + offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset); + } + if (offset >= 0) { + textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn); + textArea.Caret.DesiredXPos = double.NaN; + } + } + + void ExtendSelectionToMouse(MouseEventArgs e) + { + TextViewPosition oldPosition = textArea.Caret.Position; + if (mode == SelectionMode.Normal || mode == SelectionMode.Rectangular) { + SetCaretOffsetToMousePosition(e); + if (mode == SelectionMode.Normal && textArea.Selection is RectangleSelection) + textArea.Selection = new SimpleSelection(textArea, oldPosition, textArea.Caret.Position); + else if (mode == SelectionMode.Rectangular && !(textArea.Selection is RectangleSelection)) + textArea.Selection = new RectangleSelection(textArea, oldPosition, textArea.Caret.Position); + else + textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position); + } else if (mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine) { + var newWord = (mode == SelectionMode.WholeLine) ? GetLineAtMousePosition(e) : GetWordAtMousePosition(e); + if (newWord != SimpleSegment.Invalid) { + textArea.Selection = Selection.Create(textArea, + Math.Min(newWord.Offset, startWord.Offset), + Math.Max(newWord.EndOffset, startWord.EndOffset)); + // Set caret offset, but limit the caret to stay inside the selection. + // in whole-word selection, it's otherwise possible that we get the caret outside the + // selection - but the TextArea doesn't like that and will reset the selection, causing + // flickering. + SetCaretOffsetToMousePosition(e, textArea.Selection.SurroundingSegment); + } + } + textArea.Caret.BringCaretToView(5.0); + } + #endregion + + #region MouseLeftButtonUp + void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (mode == SelectionMode.None || e.Handled) + return; + e.Handled = true; + if (mode == SelectionMode.PossibleDragStart) { + // -> this was not a drag start (mouse didn't move after mousedown) + SetCaretOffsetToMousePosition(e); + textArea.ClearSelection(); + } else if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) { + ExtendSelectionToMouse(e); + } + mode = SelectionMode.None; + textArea.ReleaseMouseCapture(); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs new file mode 100644 index 000000000..46c560b34 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SelectionSegment.cs @@ -0,0 +1,89 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Represents a selected segment. + /// + public class SelectionSegment : ISegment + { + readonly int startOffset, endOffset; + readonly int startVC, endVC; + + /// + /// Creates a SelectionSegment from two offsets. + /// + public SelectionSegment(int startOffset, int endOffset) + { + this.startOffset = Math.Min(startOffset, endOffset); + this.endOffset = Math.Max(startOffset, endOffset); + this.startVC = this.endVC = -1; + } + + /// + /// Creates a SelectionSegment from two offsets and visual columns. + /// + public SelectionSegment(int startOffset, int startVC, int endOffset, int endVC) + { + if (startOffset < endOffset || (startOffset == endOffset && startVC <= endVC)) { + this.startOffset = startOffset; + this.startVC = startVC; + this.endOffset = endOffset; + this.endVC = endVC; + } else { + this.startOffset = endOffset; + this.startVC = endVC; + this.endOffset = startOffset; + this.endVC = startVC; + } + } + + /// + /// Gets the start offset. + /// + public int StartOffset { + get { return startOffset; } + } + + /// + /// Gets the end offset. + /// + public int EndOffset { + get { return endOffset; } + } + + /// + /// Gets the start visual column. + /// + public int StartVisualColumn { + get { return startVC; } + } + + /// + /// Gets the end visual column. + /// + public int EndVisualColumn { + get { return endVC; } + } + + /// + int ISegment.Offset { + get { return startOffset; } + } + + /// + public int Length { + get { return endOffset - startOffset; } + } + + /// + public override string ToString() + { + return string.Format("[SelectionSegment StartOffset={0}, EndOffset={1}, StartVC={2}, EndVC={3}]", startOffset, endOffset, startVC, endVC); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs new file mode 100644 index 000000000..132b09e20 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/SimpleSelection.cs @@ -0,0 +1,144 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// A simple selection. + /// + sealed class SimpleSelection : Selection + { + readonly TextViewPosition start, end; + readonly int startOffset, endOffset; + + /// + /// Creates a new SimpleSelection instance. + /// + internal SimpleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end) + : base(textArea) + { + this.start = start; + this.end = end; + this.startOffset = textArea.Document.GetOffset(start.Location); + this.endOffset = textArea.Document.GetOffset(end.Location); + } + + /// + public override IEnumerable Segments { + get { + return ExtensionMethods.Sequence(new SelectionSegment(startOffset, start.VisualColumn, endOffset, end.VisualColumn)); + } + } + + /// + public override ISegment SurroundingSegment { + get { + return new SelectionSegment(startOffset, endOffset); + } + } + + /// + public override void ReplaceSelectionWithText(string newText) + { + if (newText == null) + throw new ArgumentNullException("newText"); + using (textArea.Document.RunUpdate()) { + ISegment[] segmentsToDelete = textArea.GetDeletableSegments(this.SurroundingSegment); + for (int i = segmentsToDelete.Length - 1; i >= 0; i--) { + if (i == segmentsToDelete.Length - 1) { + if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) { + newText = AddSpacesIfRequired(newText, start, end); + } + int vc = textArea.Caret.VisualColumn; + textArea.Caret.Offset = segmentsToDelete[i].EndOffset; + if (string.IsNullOrEmpty(newText)) + textArea.Caret.VisualColumn = vc; + textArea.Document.Replace(segmentsToDelete[i], newText); + } else { + textArea.Document.Remove(segmentsToDelete[i]); + } + } + if (segmentsToDelete.Length != 0) { + textArea.ClearSelection(); + } + } + } + + public override TextViewPosition StartPosition { + get { return start; } + } + + public override TextViewPosition EndPosition { + get { return end; } + } + + /// + public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e) + { + if (e == null) + throw new ArgumentNullException("e"); + return Selection.Create( + textArea, + new TextViewPosition(textArea.Document.GetLocation(e.GetNewOffset(startOffset, AnchorMovementType.Default)), start.VisualColumn), + new TextViewPosition(textArea.Document.GetLocation(e.GetNewOffset(endOffset, AnchorMovementType.Default)), end.VisualColumn) + ); + } + + /// + public override bool IsEmpty { + get { return startOffset == endOffset; } + } + + /// + public override int Length { + get { + return Math.Abs(endOffset - startOffset); + } + } + + /// + public override Selection SetEndpoint(TextViewPosition endPosition) + { + return Create(textArea, start, endPosition); + } + + public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition) + { + var document = textArea.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + return Create(textArea, start, endPosition); + } + + /// + public override int GetHashCode() + { + unchecked { + return startOffset * 27811 + endOffset + textArea.GetHashCode(); + } + } + + /// + public override bool Equals(object obj) + { + SimpleSelection other = obj as SimpleSelection; + if (other == null) return false; + return this.start.Equals(other.start) && this.end.Equals(other.end) + && this.startOffset == other.startOffset && this.endOffset == other.endOffset + && this.textArea == other.textArea; + } + + /// + public override string ToString() + { + return "[SimpleSelection Start=" + start + " End=" + end + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs new file mode 100644 index 000000000..393a3e29c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextArea.cs @@ -0,0 +1,1048 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Indentation; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Control that wraps a TextView and adds support for user input and the caret. + /// + public class TextArea : Control, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider + { + internal readonly ImeSupport ime; + + #region Constructor + static TextArea() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextArea), + new FrameworkPropertyMetadata(typeof(TextArea))); + KeyboardNavigation.IsTabStopProperty.OverrideMetadata( + typeof(TextArea), new FrameworkPropertyMetadata(Boxes.True)); + KeyboardNavigation.TabNavigationProperty.OverrideMetadata( + typeof(TextArea), new FrameworkPropertyMetadata(KeyboardNavigationMode.None)); + FocusableProperty.OverrideMetadata( + typeof(TextArea), new FrameworkPropertyMetadata(Boxes.True)); + } + + /// + /// Creates a new TextArea instance. + /// + public TextArea() : this(new TextView()) + { + } + + /// + /// Creates a new TextArea instance. + /// + protected TextArea(TextView textView) + { + if (textView == null) + throw new ArgumentNullException("textView"); + this.textView = textView; + this.Options = textView.Options; + + selection = emptySelection = new EmptySelection(this); + + textView.Services.AddService(typeof(TextArea), this); + + textView.LineTransformers.Add(new SelectionColorizer(this)); + textView.InsertLayer(new SelectionLayer(this), KnownLayer.Selection, LayerInsertionPosition.Replace); + + caret = new Caret(this); + caret.PositionChanged += (sender, e) => RequestSelectionValidation(); + ime = new ImeSupport(this); + + leftMargins.CollectionChanged += leftMargins_CollectionChanged; + + this.DefaultInputHandler = new TextAreaDefaultInputHandler(this); + this.ActiveInputHandler = this.DefaultInputHandler; + } + #endregion + + #region InputHandler management + /// + /// Gets the default input handler. + /// + /// + public TextAreaDefaultInputHandler DefaultInputHandler { get; private set; } + + ITextAreaInputHandler activeInputHandler; + bool isChangingInputHandler; + + /// + /// Gets/Sets the active input handler. + /// This property does not return currently active stacked input handlers. Setting this property detached all stacked input handlers. + /// + /// + public ITextAreaInputHandler ActiveInputHandler { + get { return activeInputHandler; } + set { + if (value != null && value.TextArea != this) + throw new ArgumentException("The input handler was created for a different text area than this one."); + if (isChangingInputHandler) + throw new InvalidOperationException("Cannot set ActiveInputHandler recursively"); + if (activeInputHandler != value) { + isChangingInputHandler = true; + try { + // pop the whole stack + PopStackedInputHandler(stackedInputHandlers.LastOrDefault()); + Debug.Assert(stackedInputHandlers.IsEmpty); + + if (activeInputHandler != null) + activeInputHandler.Detach(); + activeInputHandler = value; + if (value != null) + value.Attach(); + } finally { + isChangingInputHandler = false; + } + if (ActiveInputHandlerChanged != null) + ActiveInputHandlerChanged(this, EventArgs.Empty); + } + } + } + + /// + /// Occurs when the ActiveInputHandler property changes. + /// + public event EventHandler ActiveInputHandlerChanged; + + ImmutableStack stackedInputHandlers = ImmutableStack.Empty; + + /// + /// Gets the list of currently active stacked input handlers. + /// + /// + public ImmutableStack StackedInputHandlers { + get { return stackedInputHandlers; } + } + + /// + /// Pushes an input handler onto the list of stacked input handlers. + /// + /// + public void PushStackedInputHandler(TextAreaStackedInputHandler inputHandler) + { + if (inputHandler == null) + throw new ArgumentNullException("inputHandler"); + stackedInputHandlers = stackedInputHandlers.Push(inputHandler); + inputHandler.Attach(); + } + + /// + /// Pops the stacked input handler (and all input handlers above it). + /// If is not found in the currently stacked input handlers, or is null, this method + /// does nothing. + /// + /// + public void PopStackedInputHandler(TextAreaStackedInputHandler inputHandler) + { + if (stackedInputHandlers.Any(i => i == inputHandler)) { + ITextAreaInputHandler oldHandler; + do { + oldHandler = stackedInputHandlers.Peek(); + stackedInputHandlers = stackedInputHandlers.Pop(); + oldHandler.Detach(); + } while (oldHandler != inputHandler); + } + } + #endregion + + #region Document property + /// + /// Document property. + /// + public static readonly DependencyProperty DocumentProperty + = TextView.DocumentProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnDocumentChanged)); + + /// + /// Gets/Sets the document displayed by the text editor. + /// + public TextDocument Document { + get { return (TextDocument)GetValue(DocumentProperty); } + set { SetValue(DocumentProperty, value); } + } + + /// + public event EventHandler DocumentChanged; + + static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextArea)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue); + } + + void OnDocumentChanged(TextDocument oldValue, TextDocument newValue) + { + if (oldValue != null) { + TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this); + TextDocumentWeakEventManager.Changed.RemoveListener(oldValue, this); + TextDocumentWeakEventManager.UpdateStarted.RemoveListener(oldValue, this); + TextDocumentWeakEventManager.UpdateFinished.RemoveListener(oldValue, this); + } + textView.Document = newValue; + if (newValue != null) { + TextDocumentWeakEventManager.Changing.AddListener(newValue, this); + TextDocumentWeakEventManager.Changed.AddListener(newValue, this); + TextDocumentWeakEventManager.UpdateStarted.AddListener(newValue, this); + TextDocumentWeakEventManager.UpdateFinished.AddListener(newValue, this); + } + // Reset caret location and selection: this is necessary because the caret/selection might be invalid + // in the new document (e.g. if new document is shorter than the old document). + caret.Location = new TextLocation(1, 1); + this.ClearSelection(); + if (DocumentChanged != null) + DocumentChanged(this, EventArgs.Empty); + CommandManager.InvalidateRequerySuggested(); + } + #endregion + + #region Options property + /// + /// Options property. + /// + public static readonly DependencyProperty OptionsProperty + = TextView.OptionsProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnOptionsChanged)); + + /// + /// Gets/Sets the document displayed by the text editor. + /// + public TextEditorOptions Options { + get { return (TextEditorOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } + + /// + /// Occurs when a text editor option has changed. + /// + public event PropertyChangedEventHandler OptionChanged; + + /// + /// Raises the event. + /// + protected virtual void OnOptionChanged(PropertyChangedEventArgs e) + { + if (OptionChanged != null) { + OptionChanged(this, e); + } + } + + static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextArea)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue); + } + + void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue) + { + if (oldValue != null) { + PropertyChangedWeakEventManager.RemoveListener(oldValue, this); + } + textView.Options = newValue; + if (newValue != null) { + PropertyChangedWeakEventManager.AddListener(newValue, this); + } + OnOptionChanged(new PropertyChangedEventArgs(null)); + } + #endregion + + #region ReceiveWeakEvent + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextDocumentWeakEventManager.Changing)) { + OnDocumentChanging(); + return true; + } else if (managerType == typeof(TextDocumentWeakEventManager.Changed)) { + OnDocumentChanged((DocumentChangeEventArgs)e); + return true; + } else if (managerType == typeof(TextDocumentWeakEventManager.UpdateStarted)) { + OnUpdateStarted(); + return true; + } else if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) { + OnUpdateFinished(); + return true; + } else if (managerType == typeof(PropertyChangedWeakEventManager)) { + OnOptionChanged((PropertyChangedEventArgs)e); + return true; + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + #endregion + + #region Caret handling on document changes + void OnDocumentChanging() + { + caret.OnDocumentChanging(); + } + + void OnDocumentChanged(DocumentChangeEventArgs e) + { + caret.OnDocumentChanged(e); + this.Selection = selection.UpdateOnDocumentChange(e); + } + + void OnUpdateStarted() + { + Document.UndoStack.PushOptional(new RestoreCaretAndSelectionUndoAction(this)); + } + + void OnUpdateFinished() + { + caret.OnDocumentUpdateFinished(); + } + + sealed class RestoreCaretAndSelectionUndoAction : IUndoableOperation + { + // keep textarea in weak reference because the IUndoableOperation is stored with the document + WeakReference textAreaReference; + TextViewPosition caretPosition; + Selection selection; + + public RestoreCaretAndSelectionUndoAction(TextArea textArea) + { + this.textAreaReference = new WeakReference(textArea); + // Just save the old caret position, no need to validate here. + // If we restore it, we'll validate it anyways. + this.caretPosition = textArea.Caret.NonValidatedPosition; + this.selection = textArea.Selection; + } + + public void Undo() + { + TextArea textArea = (TextArea)textAreaReference.Target; + if (textArea != null) { + textArea.Caret.Position = caretPosition; + textArea.Selection = selection; + } + } + + public void Redo() + { + // redo=undo: we just restore the caret/selection state + Undo(); + } + } + #endregion + + #region TextView property + readonly TextView textView; + IScrollInfo scrollInfo; + + /// + /// Gets the text view used to display text in this text area. + /// + public TextView TextView { + get { + return textView; + } + } + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + scrollInfo = textView; + ApplyScrollInfo(); + } + #endregion + + #region Selection property + internal readonly Selection emptySelection; + Selection selection; + + /// + /// Occurs when the selection has changed. + /// + public event EventHandler SelectionChanged; + + /// + /// Gets/Sets the selection in this text area. + /// + + public Selection Selection { + get { return selection; } + set { + if (value == null) + throw new ArgumentNullException("value"); + if (value.textArea != this) + throw new ArgumentException("Cannot use a Selection instance that belongs to another text area."); + if (!object.Equals(selection, value)) { +// Debug.WriteLine("Selection change from " + selection + " to " + value); + if (textView != null) { + ISegment oldSegment = selection.SurroundingSegment; + ISegment newSegment = value.SurroundingSegment; + if (!Selection.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) { + // perf optimization: + // When a simple selection changes, don't redraw the whole selection, but only the changed parts. + int oldSegmentOffset = oldSegment.Offset; + int newSegmentOffset = newSegment.Offset; + if (oldSegmentOffset != newSegmentOffset) { + textView.Redraw(Math.Min(oldSegmentOffset, newSegmentOffset), + Math.Abs(oldSegmentOffset - newSegmentOffset), + DispatcherPriority.Background); + } + int oldSegmentEndOffset = oldSegment.EndOffset; + int newSegmentEndOffset = newSegment.EndOffset; + if (oldSegmentEndOffset != newSegmentEndOffset) { + textView.Redraw(Math.Min(oldSegmentEndOffset, newSegmentEndOffset), + Math.Abs(oldSegmentEndOffset - newSegmentEndOffset), + DispatcherPriority.Background); + } + } else { + textView.Redraw(oldSegment, DispatcherPriority.Background); + textView.Redraw(newSegment, DispatcherPriority.Background); + } + } + selection = value; + if (SelectionChanged != null) + SelectionChanged(this, EventArgs.Empty); + // a selection change causes commands like copy/paste/etc. to change status + CommandManager.InvalidateRequerySuggested(); + } + } + } + + /// + /// Clears the current selection. + /// + public void ClearSelection() + { + this.Selection = emptySelection; + } + + /// + /// The property. + /// + public static readonly DependencyProperty SelectionBrushProperty = + DependencyProperty.Register("SelectionBrush", typeof(Brush), typeof(TextArea)); + + /// + /// Gets/Sets the background brush used for the selection. + /// + public Brush SelectionBrush { + get { return (Brush)GetValue(SelectionBrushProperty); } + set { SetValue(SelectionBrushProperty, value); } + } + + /// + /// The property. + /// + public static readonly DependencyProperty SelectionForegroundProperty = + DependencyProperty.Register("SelectionForeground", typeof(Brush), typeof(TextArea)); + + /// + /// Gets/Sets the foreground brush used selected text. + /// + public Brush SelectionForeground { + get { return (Brush)GetValue(SelectionForegroundProperty); } + set { SetValue(SelectionForegroundProperty, value); } + } + + /// + /// The property. + /// + public static readonly DependencyProperty SelectionBorderProperty = + DependencyProperty.Register("SelectionBorder", typeof(Pen), typeof(TextArea)); + + /// + /// Gets/Sets the background brush used for the selection. + /// + public Pen SelectionBorder { + get { return (Pen)GetValue(SelectionBorderProperty); } + set { SetValue(SelectionBorderProperty, value); } + } + + /// + /// The property. + /// + public static readonly DependencyProperty SelectionCornerRadiusProperty = + DependencyProperty.Register("SelectionCornerRadius", typeof(double), typeof(TextArea), + new FrameworkPropertyMetadata(3.0)); + + /// + /// Gets/Sets the corner radius of the selection. + /// + public double SelectionCornerRadius { + get { return (double)GetValue(SelectionCornerRadiusProperty); } + set { SetValue(SelectionCornerRadiusProperty, value); } + } + #endregion + + #region Force caret to stay inside selection + bool ensureSelectionValidRequested; + int allowCaretOutsideSelection; + + void RequestSelectionValidation() + { + if (!ensureSelectionValidRequested && allowCaretOutsideSelection == 0) { + ensureSelectionValidRequested = true; + Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(EnsureSelectionValid)); + } + } + + /// + /// Code that updates only the caret but not the selection can cause confusion when + /// keys like 'Delete' delete the (possibly invisible) selected text and not the + /// text around the caret. + /// + /// So we'll ensure that the caret is inside the selection. + /// (when the caret is not in the selection, we'll clear the selection) + /// + /// This method is invoked using the Dispatcher so that code may temporarily violate this rule + /// (e.g. most 'extend selection' methods work by first setting the caret, then the selection), + /// it's sufficient to fix it after any event handlers have run. + /// + void EnsureSelectionValid() + { + ensureSelectionValidRequested = false; + if (allowCaretOutsideSelection == 0) { + if (!selection.IsEmpty && !selection.Contains(caret.Offset)) { + Debug.WriteLine("Resetting selection because caret is outside"); + this.ClearSelection(); + } + } + } + + /// + /// Temporarily allows positioning the caret outside the selection. + /// Dispose the returned IDisposable to revert the allowance. + /// + /// + /// The text area only forces the caret to be inside the selection when other events + /// have finished running (using the dispatcher), so you don't have to use this method + /// for temporarily positioning the caret in event handlers. + /// This method is only necessary if you want to run the WPF dispatcher, e.g. if you + /// perform a drag'n'drop operation. + /// + public IDisposable AllowCaretOutsideSelection() + { + VerifyAccess(); + allowCaretOutsideSelection++; + return new CallbackOnDispose( + delegate { + VerifyAccess(); + allowCaretOutsideSelection--; + RequestSelectionValidation(); + }); + } + #endregion + + #region Properties + readonly Caret caret; + + /// + /// Gets the Caret used for this text area. + /// + public Caret Caret { + get { return caret; } + } + + ObservableCollection leftMargins = new ObservableCollection(); + + /// + /// Gets the collection of margins displayed to the left of the text view. + /// + public ObservableCollection LeftMargins { + get { + return leftMargins; + } + } + + void leftMargins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) { + foreach (ITextViewConnect c in e.OldItems.OfType()) { + c.RemoveFromTextView(textView); + } + } + if (e.NewItems != null) { + foreach (ITextViewConnect c in e.NewItems.OfType()) { + c.AddToTextView(textView); + } + } + } + + IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance; + + /// + /// Gets/Sets an object that provides read-only sections for the text area. + /// + public IReadOnlySectionProvider ReadOnlySectionProvider { + get { return readOnlySectionProvider; } + set { + if (value == null) + throw new ArgumentNullException("value"); + readOnlySectionProvider = value; + CommandManager.InvalidateRequerySuggested(); // the read-only status effects Paste.CanExecute and the IME + } + } + #endregion + + #region IScrollInfo implementation + ScrollViewer scrollOwner; + bool canVerticallyScroll, canHorizontallyScroll; + + void ApplyScrollInfo() + { + if (scrollInfo != null) { + scrollInfo.ScrollOwner = scrollOwner; + scrollInfo.CanVerticallyScroll = canVerticallyScroll; + scrollInfo.CanHorizontallyScroll = canHorizontallyScroll; + scrollOwner = null; + } + } + + bool IScrollInfo.CanVerticallyScroll { + get { return scrollInfo != null ? scrollInfo.CanVerticallyScroll : false; } + set { + canVerticallyScroll = value; + if (scrollInfo != null) + scrollInfo.CanVerticallyScroll = value; + } + } + + bool IScrollInfo.CanHorizontallyScroll { + get { return scrollInfo != null ? scrollInfo.CanHorizontallyScroll : false; } + set { + canHorizontallyScroll = value; + if (scrollInfo != null) + scrollInfo.CanHorizontallyScroll = value; + } + } + + double IScrollInfo.ExtentWidth { + get { return scrollInfo != null ? scrollInfo.ExtentWidth : 0; } + } + + double IScrollInfo.ExtentHeight { + get { return scrollInfo != null ? scrollInfo.ExtentHeight : 0; } + } + + double IScrollInfo.ViewportWidth { + get { return scrollInfo != null ? scrollInfo.ViewportWidth : 0; } + } + + double IScrollInfo.ViewportHeight { + get { return scrollInfo != null ? scrollInfo.ViewportHeight : 0; } + } + + double IScrollInfo.HorizontalOffset { + get { return scrollInfo != null ? scrollInfo.HorizontalOffset : 0; } + } + + double IScrollInfo.VerticalOffset { + get { return scrollInfo != null ? scrollInfo.VerticalOffset : 0; } + } + + ScrollViewer IScrollInfo.ScrollOwner { + get { return scrollInfo != null ? scrollInfo.ScrollOwner : null; } + set { + if (scrollInfo != null) + scrollInfo.ScrollOwner = value; + else + scrollOwner = value; + } + } + + void IScrollInfo.LineUp() + { + if (scrollInfo != null) scrollInfo.LineUp(); + } + + void IScrollInfo.LineDown() + { + if (scrollInfo != null) scrollInfo.LineDown(); + } + + void IScrollInfo.LineLeft() + { + if (scrollInfo != null) scrollInfo.LineLeft(); + } + + void IScrollInfo.LineRight() + { + if (scrollInfo != null) scrollInfo.LineRight(); + } + + void IScrollInfo.PageUp() + { + if (scrollInfo != null) scrollInfo.PageUp(); + } + + void IScrollInfo.PageDown() + { + if (scrollInfo != null) scrollInfo.PageDown(); + } + + void IScrollInfo.PageLeft() + { + if (scrollInfo != null) scrollInfo.PageLeft(); + } + + void IScrollInfo.PageRight() + { + if (scrollInfo != null) scrollInfo.PageRight(); + } + + void IScrollInfo.MouseWheelUp() + { + if (scrollInfo != null) scrollInfo.MouseWheelUp(); + } + + void IScrollInfo.MouseWheelDown() + { + if (scrollInfo != null) scrollInfo.MouseWheelDown(); + } + + void IScrollInfo.MouseWheelLeft() + { + if (scrollInfo != null) scrollInfo.MouseWheelLeft(); + } + + void IScrollInfo.MouseWheelRight() + { + if (scrollInfo != null) scrollInfo.MouseWheelRight(); + } + + void IScrollInfo.SetHorizontalOffset(double offset) + { + if (scrollInfo != null) scrollInfo.SetHorizontalOffset(offset); + } + + void IScrollInfo.SetVerticalOffset(double offset) + { + if (scrollInfo != null) scrollInfo.SetVerticalOffset(offset); + } + + Rect IScrollInfo.MakeVisible(System.Windows.Media.Visual visual, Rect rectangle) + { + if (scrollInfo != null) + return scrollInfo.MakeVisible(visual, rectangle); + else + return Rect.Empty; + } + #endregion + + #region Focus Handling (Show/Hide Caret) + /// + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + Focus(); + } + + /// + protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnGotKeyboardFocus(e); + // First activate IME, then show caret + ime.OnGotKeyboardFocus(e); + caret.Show(); + } + + /// + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnLostKeyboardFocus(e); + caret.Hide(); + ime.OnLostKeyboardFocus(e); + } + #endregion + + #region OnTextInput / RemoveSelectedText / ReplaceSelectionWithText + /// + /// Occurs when the TextArea receives text input. + /// This is like the event, + /// but occurs immediately before the TextArea handles the TextInput event. + /// + public event TextCompositionEventHandler TextEntering; + + /// + /// Occurs when the TextArea receives text input. + /// This is like the event, + /// but occurs immediately after the TextArea handles the TextInput event. + /// + public event TextCompositionEventHandler TextEntered; + + /// + /// Raises the TextEntering event. + /// + protected virtual void OnTextEntering(TextCompositionEventArgs e) + { + if (TextEntering != null) { + TextEntering(this, e); + } + } + + /// + /// Raises the TextEntered event. + /// + protected virtual void OnTextEntered(TextCompositionEventArgs e) + { + if (TextEntered != null) { + TextEntered(this, e); + } + } + + /// + protected override void OnTextInput(TextCompositionEventArgs e) + { + //Debug.WriteLine("TextInput: Text='" + e.Text + "' SystemText='" + e.SystemText + "' ControlText='" + e.ControlText + "'"); + base.OnTextInput(e); + if (!e.Handled && this.Document != null) { + if (string.IsNullOrEmpty(e.Text) || e.Text == "\x1b" || e.Text == "\b") { + // ASCII 0x1b = ESC. + // WPF produces a TextInput event with that old ASCII control char + // when Escape is pressed. We'll just ignore it. + + // A deadkey followed by backspace causes a textinput event for the BS character. + + // Similarly, some shortcuts like Alt+Space produce an empty TextInput event. + // We have to ignore those (not handle them) to keep the shortcut working. + return; + } + PerformTextInput(e); + e.Handled = true; + } + } + + /// + /// Performs text input. + /// This raises the event, replaces the selection with the text, + /// and then raises the event. + /// + public void PerformTextInput(string text) + { + TextComposition textComposition = new TextComposition(InputManager.Current, this, text); + TextCompositionEventArgs e = new TextCompositionEventArgs(Keyboard.PrimaryDevice, textComposition); + e.RoutedEvent = TextInputEvent; + PerformTextInput(e); + } + + /// + /// Performs text input. + /// This raises the event, replaces the selection with the text, + /// and then raises the event. + /// + public void PerformTextInput(TextCompositionEventArgs e) + { + if (e == null) + throw new ArgumentNullException("e"); + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + OnTextEntering(e); + if (!e.Handled) { + if (e.Text == "\n" || e.Text == "\r" || e.Text == "\r\n") + ReplaceSelectionWithNewLine(); + else + ReplaceSelectionWithText(e.Text); + OnTextEntered(e); + caret.BringCaretToView(); + } + } + + void ReplaceSelectionWithNewLine() + { + string newLine = TextUtilities.GetNewLineFromDocument(this.Document, this.Caret.Line); + using (this.Document.RunUpdate()) { + ReplaceSelectionWithText(newLine); + if (this.IndentationStrategy != null) { + DocumentLine line = this.Document.GetLineByNumber(this.Caret.Line); + ISegment[] deletable = GetDeletableSegments(line); + if (deletable.Length == 1 && deletable[0].Offset == line.Offset && deletable[0].Length == line.Length) { + // use indentation strategy only if the line is not read-only + this.IndentationStrategy.IndentLine(this.Document, line); + } + } + } + } + + internal void RemoveSelectedText() + { + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + selection.ReplaceSelectionWithText(string.Empty); + #if DEBUG + if (!selection.IsEmpty) { + foreach (ISegment s in selection.Segments) { + Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0); + } + } + #endif + } + + internal void ReplaceSelectionWithText(string newText) + { + if (newText == null) + throw new ArgumentNullException("newText"); + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + selection.ReplaceSelectionWithText(newText); + } + + internal ISegment[] GetDeletableSegments(ISegment segment) + { + var deletableSegments = this.ReadOnlySectionProvider.GetDeletableSegments(segment); + if (deletableSegments == null) + throw new InvalidOperationException("ReadOnlySectionProvider.GetDeletableSegments returned null"); + var array = deletableSegments.ToArray(); + int lastIndex = segment.Offset; + for (int i = 0; i < array.Length; i++) { + if (array[i].Offset < lastIndex) + throw new InvalidOperationException("ReadOnlySectionProvider returned incorrect segments (outside of input segment / wrong order)"); + lastIndex = array[i].EndOffset; + } + if (lastIndex > segment.EndOffset) + throw new InvalidOperationException("ReadOnlySectionProvider returned incorrect segments (outside of input segment / wrong order)"); + return array; + } + #endregion + + #region IndentationStrategy property + /// + /// IndentationStrategy property. + /// + public static readonly DependencyProperty IndentationStrategyProperty = + DependencyProperty.Register("IndentationStrategy", typeof(IIndentationStrategy), typeof(TextArea), + new FrameworkPropertyMetadata(new DefaultIndentationStrategy())); + + /// + /// Gets/Sets the indentation strategy used when inserting new lines. + /// + public IIndentationStrategy IndentationStrategy { + get { return (IIndentationStrategy)GetValue(IndentationStrategyProperty); } + set { SetValue(IndentationStrategyProperty, value); } + } + #endregion + + #region OnKeyDown/OnKeyUp + /// + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + base.OnPreviewKeyDown(e); + foreach (TextAreaStackedInputHandler h in stackedInputHandlers) { + if (e.Handled) + break; + h.OnPreviewKeyDown(e); + } + } + + /// + protected override void OnPreviewKeyUp(KeyEventArgs e) + { + base.OnPreviewKeyUp(e); + foreach (TextAreaStackedInputHandler h in stackedInputHandlers) { + if (e.Handled) + break; + h.OnPreviewKeyUp(e); + } + } + + // Make life easier for text editor extensions that use a different cursor based on the pressed modifier keys. + /// + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + TextView.InvalidateCursor(); + } + + /// + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + TextView.InvalidateCursor(); + } + #endregion + + /// + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + // accept clicks even where the text area draws no background + return new PointHitTestResult(this, hitTestParameters.HitPoint); + } + + /// + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + if (e.Property == SelectionBrushProperty + || e.Property == SelectionBorderProperty + || e.Property == SelectionForegroundProperty + || e.Property == SelectionCornerRadiusProperty) + { + textView.Redraw(); + } + } + + /// + /// Gets the requested service. + /// + /// Returns the requested service instance, or null if the service cannot be found. + public virtual object GetService(Type serviceType) + { + return textView.Services.GetService(serviceType); + } + + /// + /// Occurs when text inside the TextArea was copied. + /// + public event EventHandler TextCopied; + + internal void OnTextCopied(TextEventArgs e) + { + if (TextCopied != null) + TextCopied(this, e); + } + } + + /// + /// EventArgs with text. + /// + [Serializable] + public class TextEventArgs : EventArgs + { + string text; + + /// + /// Gets the text. + /// + public string Text { + get { + return text; + } + } + + /// + /// Creates a new TextEventArgs instance. + /// + public TextEventArgs(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + this.text = text; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs new file mode 100644 index 000000000..7101d16c8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaDefaultInputHandlers.cs @@ -0,0 +1,120 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Contains the predefined input handlers. + /// + public class TextAreaDefaultInputHandler : TextAreaInputHandler + { + /// + /// Gets the caret navigation input handler. + /// + public TextAreaInputHandler CaretNavigation { get; private set; } + + /// + /// Gets the editing input handler. + /// + public TextAreaInputHandler Editing { get; private set; } + + /// + /// Gets the mouse selection input handler. + /// + public ITextAreaInputHandler MouseSelection { get; private set; } + + /// + /// Creates a new TextAreaDefaultInputHandler instance. + /// + public TextAreaDefaultInputHandler(TextArea textArea) : base(textArea) + { + this.NestedInputHandlers.Add(CaretNavigation = CaretNavigationCommandHandler.Create(textArea)); + this.NestedInputHandlers.Add(Editing = EditingCommandHandler.Create(textArea)); + this.NestedInputHandlers.Add(MouseSelection = new SelectionMouseHandler(textArea)); + + this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, ExecuteUndo, CanExecuteUndo)); + this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo, ExecuteRedo, CanExecuteRedo)); + } + + internal static KeyBinding CreateFrozenKeyBinding(ICommand command, ModifierKeys modifiers, Key key) + { + KeyBinding kb = new KeyBinding(command, key, modifiers); + // Mark KeyBindings as frozen because they're shared between multiple editor instances. + // KeyBinding derives from Freezable only in .NET 4, so we have to use this little trick: + Freezable f = ((object)kb) as Freezable; + if (f != null) + f.Freeze(); + return kb; + } + + internal static void WorkaroundWPFMemoryLeak(List inputBindings) + { + // Work around WPF memory leak: + // KeyBinding retains a reference to whichever UIElement it is used in first. + // Using a dummy element for this purpose ensures that we don't leak + // a real text editor (with a potentially large document). + UIElement dummyElement = new UIElement(); + dummyElement.InputBindings.AddRange(inputBindings); + } + + #region Undo / Redo + UndoStack GetUndoStack() + { + TextDocument document = this.TextArea.Document; + if (document != null) + return document.UndoStack; + else + return null; + } + + void ExecuteUndo(object sender, ExecutedRoutedEventArgs e) + { + var undoStack = GetUndoStack(); + if (undoStack != null) { + if (undoStack.CanUndo) { + undoStack.Undo(); + this.TextArea.Caret.BringCaretToView(); + } + e.Handled = true; + } + } + + void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e) + { + var undoStack = GetUndoStack(); + if (undoStack != null) { + e.Handled = true; + e.CanExecute = undoStack.CanUndo; + } + } + + void ExecuteRedo(object sender, ExecutedRoutedEventArgs e) + { + var undoStack = GetUndoStack(); + if (undoStack != null) { + if (undoStack.CanRedo) { + undoStack.Redo(); + this.TextArea.Caret.BringCaretToView(); + } + e.Handled = true; + } + } + + void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e) + { + var undoStack = GetUndoStack(); + if (undoStack != null) { + e.Handled = true; + e.CanExecute = undoStack.CanRedo; + } + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs new file mode 100644 index 000000000..3256875be --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextAreaInputHandler.cs @@ -0,0 +1,242 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Windows.Input; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// A set of input bindings and event handlers for the text area. + /// + /// + /// + /// There is one active input handler per text area (), plus + /// a number of active stacked input handlers. + /// + /// + /// The text area also stores a reference to a default input handler, but that is not necessarily active. + /// + /// + /// Stacked input handlers work in addition to the set of currently active handlers (without detaching them). + /// They are detached in the reverse order of being attached. + /// + /// + public interface ITextAreaInputHandler + { + /// + /// Gets the text area that the input handler belongs to. + /// + TextArea TextArea { + get; + } + + /// + /// Attaches an input handler to the text area. + /// + void Attach(); + + /// + /// Detaches the input handler from the text area. + /// + void Detach(); + } + + /// + /// Stacked input handler. + /// Uses OnEvent-methods instead of registering event handlers to ensure that the events are handled in the correct order. + /// + public abstract class TextAreaStackedInputHandler : ITextAreaInputHandler + { + readonly TextArea textArea; + + /// + public TextArea TextArea { + get { return textArea; } + } + + /// + /// Creates a new TextAreaInputHandler. + /// + protected TextAreaStackedInputHandler(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + } + + /// + public virtual void Attach() + { + } + + /// + public virtual void Detach() + { + } + + /// + /// Called for the PreviewKeyDown event. + /// + public virtual void OnPreviewKeyDown(KeyEventArgs e) + { + } + + /// + /// Called for the PreviewKeyUp event. + /// + public virtual void OnPreviewKeyUp(KeyEventArgs e) + { + } + } + + /// + /// Default-implementation of . + /// + /// + public class TextAreaInputHandler : ITextAreaInputHandler + { + readonly ObserveAddRemoveCollection commandBindings; + readonly ObserveAddRemoveCollection inputBindings; + readonly ObserveAddRemoveCollection nestedInputHandlers; + readonly TextArea textArea; + bool isAttached; + + /// + /// Creates a new TextAreaInputHandler. + /// + public TextAreaInputHandler(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + commandBindings = new ObserveAddRemoveCollection(CommandBinding_Added, CommandBinding_Removed); + inputBindings = new ObserveAddRemoveCollection(InputBinding_Added, InputBinding_Removed); + nestedInputHandlers = new ObserveAddRemoveCollection(NestedInputHandler_Added, NestedInputHandler_Removed); + } + + /// + public TextArea TextArea { + get { return textArea; } + } + + /// + /// Gets whether the input handler is currently attached to the text area. + /// + public bool IsAttached { + get { return isAttached; } + } + + #region CommandBindings / InputBindings + /// + /// Gets the command bindings of this input handler. + /// + public ICollection CommandBindings { + get { return commandBindings; } + } + + void CommandBinding_Added(CommandBinding commandBinding) + { + if (isAttached) + textArea.CommandBindings.Add(commandBinding); + } + + void CommandBinding_Removed(CommandBinding commandBinding) + { + if (isAttached) + textArea.CommandBindings.Remove(commandBinding); + } + + /// + /// Gets the input bindings of this input handler. + /// + public ICollection InputBindings { + get { return inputBindings; } + } + + void InputBinding_Added(InputBinding inputBinding) + { + if (isAttached) + textArea.InputBindings.Add(inputBinding); + } + + void InputBinding_Removed(InputBinding inputBinding) + { + if (isAttached) + textArea.InputBindings.Remove(inputBinding); + } + + /// + /// Adds a command and input binding. + /// + /// The command ID. + /// The modifiers of the keyboard shortcut. + /// The key of the keyboard shortcut. + /// The event handler to run when the command is executed. + public void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler) + { + this.CommandBindings.Add(new CommandBinding(command, handler)); + this.InputBindings.Add(new KeyBinding(command, key, modifiers)); + } + #endregion + + #region NestedInputHandlers + /// + /// Gets the collection of nested input handlers. NestedInputHandlers are activated and deactivated + /// together with this input handler. + /// + public ICollection NestedInputHandlers { + get { return nestedInputHandlers; } + } + + void NestedInputHandler_Added(ITextAreaInputHandler handler) + { + if (handler == null) + throw new ArgumentNullException("handler"); + if (handler.TextArea != textArea) + throw new ArgumentException("The nested handler must be working for the same text area!"); + if (isAttached) + handler.Attach(); + } + + void NestedInputHandler_Removed(ITextAreaInputHandler handler) + { + if (isAttached) + handler.Detach(); + } + #endregion + + #region Attach/Detach + /// + public virtual void Attach() + { + if (isAttached) + throw new InvalidOperationException("Input handler is already attached"); + isAttached = true; + + textArea.CommandBindings.AddRange(commandBindings); + textArea.InputBindings.AddRange(inputBindings); + foreach (ITextAreaInputHandler handler in nestedInputHandlers) + handler.Attach(); + } + + /// + public virtual void Detach() + { + if (!isAttached) + throw new InvalidOperationException("Input handler is not attached"); + isAttached = false; + + foreach (CommandBinding b in commandBindings) + textArea.CommandBindings.Remove(b); + foreach (InputBinding b in inputBindings) + textArea.InputBindings.Remove(b); + foreach (ITextAreaInputHandler handler in nestedInputHandlers) + handler.Detach(); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs new file mode 100644 index 000000000..c582301d4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/TextSegmentReadOnlySectionProvider.cs @@ -0,0 +1,80 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Editing +{ + /// + /// Implementation for that stores the segments + /// in a . + /// + public class TextSegmentReadOnlySectionProvider : IReadOnlySectionProvider where T : TextSegment + { + readonly TextSegmentCollection segments; + + /// + /// Gets the collection storing the read-only segments. + /// + public TextSegmentCollection Segments { + get { return segments; } + } + + /// + /// Creates a new TextSegmentReadOnlySectionProvider instance for the specified document. + /// + public TextSegmentReadOnlySectionProvider(TextDocument textDocument) + { + segments = new TextSegmentCollection(textDocument); + } + + /// + /// Creates a new TextSegmentReadOnlySectionProvider instance using the specified TextSegmentCollection. + /// + public TextSegmentReadOnlySectionProvider(TextSegmentCollection segments) + { + if (segments == null) + throw new ArgumentNullException("segments"); + this.segments = segments; + } + + /// + /// Gets whether insertion is possible at the specified offset. + /// + public virtual bool CanInsert(int offset) + { + foreach (TextSegment segment in segments.FindSegmentsContaining(offset)) { + if (segment.StartOffset < offset && offset < segment.EndOffset) + return false; + } + return true; + } + + /// + /// Gets the deletable segments inside the given segment. + /// + public virtual IEnumerable GetDeletableSegments(ISegment segment) + { + if (segment == null) + throw new ArgumentNullException("segment"); + + int readonlyUntil = segment.Offset; + foreach (TextSegment ts in segments.FindOverlappingSegments(segment)) { + int start = ts.StartOffset; + int end = start + ts.Length; + if (start > readonlyUntil) { + yield return new SimpleSegment(readonlyUntil, start - readonlyUntil); + } + if (end > readonlyUntil) { + readonlyUntil = end; + } + } + int endOffset = segment.EndOffset; + if (readonlyUntil < endOffset) { + yield return new SimpleSegment(readonlyUntil, endOffset - readonlyUntil); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs new file mode 100644 index 000000000..1605ff281 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors +{ + internal static class ExtensionMethodsNoName + { + public static String Remove(this String str, String pattern) + { + return Regex.Replace(str, pattern, ""); + } + + public static String GetFriendlyName(this Type type) + { + String name = type.Name; + + if (type.IsGenericType) + { + List args = new List(); + + foreach (var lGenericArgument in type.GetGenericTypeDefinition().GetGenericArguments()) + { + args.Add(lGenericArgument.Name); + } + + String gArgs = String.Join(",", args); + + name = $"{new String(type.Name.TakeWhile(x => x != '`').ToArray())}<{gArgs}>"; + } + + return name; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs new file mode 100644 index 000000000..42eddba47 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/AbstractFoldingStrategy.cs @@ -0,0 +1,30 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; +using System.Collections.Generic; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// Base class for folding strategies. + /// + public abstract class AbstractFoldingStrategy + { + /// + /// Create s for the specified document and updates the folding manager with them. + /// + public void UpdateFoldings(FoldingManager manager, TextDocument document) + { + int firstErrorOffset; + IEnumerable foldings = CreateNewFoldings(document, out firstErrorOffset); + manager.UpdateFoldings(foldings, firstErrorOffset); + } + + /// + /// Create s for the specified document. + /// + public abstract IEnumerable CreateNewFoldings(TextDocument document, out int firstErrorOffset); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs new file mode 100644 index 000000000..0b26184ee --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/BraceFoldingStrategy.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// Allows producing foldings from a document based on braces. + /// + public class BraceFoldingStrategy + { + private class FoldingDefinition + { + public String Open { get; set; } + public String Close { get; set; } + + public FoldingDefinition(String open, String close) + { + Open = open; + Close = close; + } + } + + private class FoldingOffset + { + public FoldingDefinition Definition { get; set; } + public int Offset { get; set; } + } + + private List foldings = new List() + { + new FoldingDefinition("namespace","}"), + new FoldingDefinition("public","}"), + new FoldingDefinition("private","}"), + new FoldingDefinition("internal","}"), + new FoldingDefinition("if","}"), + new FoldingDefinition("class","}"), + new FoldingDefinition("while","}"), + new FoldingDefinition("static void","}"), + new FoldingDefinition("/// ","/// "), + }; + + /// + /// Gets/Sets the opening brace. The default value is '{'. + /// + public char OpeningBrace { get; set; } + + /// + /// Gets/Sets the closing brace. The default value is '}'. + /// + public char ClosingBrace { get; set; } + + /// + /// Creates a new BraceFoldingStrategy. + /// + public BraceFoldingStrategy() + { + this.OpeningBrace = '{'; + this.ClosingBrace = '}'; + } + + public void UpdateFoldings(FoldingManager manager, TextDocument document) + { + int firstErrorOffset; + IEnumerable newFoldings = CreateNewFoldings(document, out firstErrorOffset); + manager.UpdateFoldings(newFoldings, firstErrorOffset); + } + + /// + /// Create s for the specified document. + /// + public IEnumerable CreateNewFoldings(TextDocument document, out int firstErrorOffset) + { + firstErrorOffset = -1; + return CreateNewFoldings(document); + } + + /// + /// Create s for the specified document. + /// + public IEnumerable CreateNewFoldings(ITextSource document) + { + List newFoldings = new List(); + + Stack startOffsets = new Stack(); + int lastNewLineOffset = 0; + char openingBrace = this.OpeningBrace; + char closingBrace = this.ClosingBrace; + + var doc = document as TextDocument; + + String current_line = String.Empty; + + foreach (var line in doc.Lines) + { + String lineTextFull = doc.GetText(line); + String lineText = lineTextFull.TrimStart('\t', ' ').TrimEnd('\t', ' '); + + var open_definition = foldings.SingleOrDefault(x => lineText.StartsWith(x.Open + " ") || lineText == x.Open); + + if (open_definition != null) + { + current_line = lineTextFull; + startOffsets.Push(new FoldingOffset() + { + Definition = open_definition, + Offset = line.EndOffset + }); + } + else if (foldings.Any(x => lineText.EndsWith(x.Close))) + { + if (startOffsets.Count > 0) + { + var startOffset = startOffsets.Pop(); + //if (startOffset < lastNewLineOffset) + //{ + newFoldings.Add(new NewFolding(startOffset.Offset, Math.Min(line.EndOffset, doc.TextLength)) + { + //Name = current_line, + }); + } + //} + } + else if (lineText.EndsWith("\n") || lineText.EndsWith("\r")) + { + lastNewLineOffset = line.Offset + 1; + } + } + + //for (int i = 0; i < document.TextLength; i++) + //{ + // char c = document.GetCharAt(i); + // if (c == openingBrace) + // { + // startOffsets.Push(i); + // } + // else if (c == closingBrace && startOffsets.Count > 0) + // { + // int startOffset = startOffsets.Pop(); + // // don't fold if opening and closing brace are on the same line + // if (startOffset < lastNewLineOffset) + // { + // newFoldings.Add(new NewFolding(startOffset, i + 1)); + // } + // } + // else if (c == '\n' || c == '\r') + // { + // lastNewLineOffset = i + 1; + // } + //} + + newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset)); + return newFoldings; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs new file mode 100644 index 000000000..76a1da101 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs @@ -0,0 +1,194 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// A that produces line elements for folded s. + /// + public sealed class FoldingElementGenerator : VisualLineElementGenerator, ITextViewConnect + { + readonly List textViews = new List(); + FoldingManager foldingManager; + + #region FoldingManager property / connecting with TextView + /// + /// Gets/Sets the folding manager from which the foldings should be shown. + /// + public FoldingManager FoldingManager { + get { + return foldingManager; + } + set { + if (foldingManager != value) { + if (foldingManager != null) { + foreach (TextView v in textViews) + foldingManager.RemoveFromTextView(v); + } + foldingManager = value; + if (foldingManager != null) { + foreach (TextView v in textViews) + foldingManager.AddToTextView(v); + } + } + } + } + + void ITextViewConnect.AddToTextView(TextView textView) + { + textViews.Add(textView); + if (foldingManager != null) + foldingManager.AddToTextView(textView); + } + + void ITextViewConnect.RemoveFromTextView(TextView textView) + { + textViews.Remove(textView); + if (foldingManager != null) + foldingManager.RemoveFromTextView(textView); + } + #endregion + + /// + public override void StartGeneration(ITextRunConstructionContext context) + { + base.StartGeneration(context); + if (foldingManager != null) { + if (!foldingManager.textViews.Contains(context.TextView)) + throw new ArgumentException("Invalid TextView"); + if (context.Document != foldingManager.document) + throw new ArgumentException("Invalid document"); + } + } + + /// + public override int GetFirstInterestedOffset(int startOffset) + { + if (foldingManager != null) { + foreach (FoldingSection fs in foldingManager.GetFoldingsContaining(startOffset)) { + // Test whether we're currently within a folded folding (that didn't just end). + // If so, create the fold marker immediately. + // This is necessary if the actual beginning of the fold marker got skipped due to another VisualElementGenerator. + if (fs.IsFolded && fs.EndOffset > startOffset) { + //return startOffset; + } + } + return foldingManager.GetNextFoldedFoldingStart(startOffset); + } else { + return -1; + } + } + + /// + public override VisualLineElement ConstructElement(int offset) + { + if (foldingManager == null) + return null; + int foldedUntil = -1; + FoldingSection foldingSection = null; + foreach (FoldingSection fs in foldingManager.GetFoldingsContaining(offset)) { + if (fs.IsFolded) { + if (fs.EndOffset > foldedUntil) { + foldedUntil = fs.EndOffset; + foldingSection = fs; + } + } + } + if (foldedUntil > offset && foldingSection != null) { + // Handle overlapping foldings: if there's another folded folding + // (starting within the foldingSection) that continues after the end of the folded section, + // then we'll extend our fold element to cover that overlapping folding. + bool foundOverlappingFolding; + do { + foundOverlappingFolding = false; + foreach (FoldingSection fs in FoldingManager.GetFoldingsContaining(foldedUntil)) { + if (fs.IsFolded && fs.EndOffset > foldedUntil) { + foldedUntil = fs.EndOffset; + foundOverlappingFolding = true; + } + } + } while (foundOverlappingFolding); + + string title = foldingSection.Title; + if (string.IsNullOrEmpty(title)) + title = "..."; + var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties); + p.SetForegroundBrush(textBrush); + var textFormatter = TextFormatterFactory.Create(CurrentContext.TextView); + var text = FormattedTextElement.PrepareText(textFormatter, title, p); + return new FoldingLineElement(foldingSection, text, foldedUntil - offset) { textBrush = textBrush }; + } else { + return null; + } + } + + sealed class FoldingLineElement : FormattedTextElement + { + readonly FoldingSection fs; + + internal Brush textBrush; + + public FoldingLineElement(FoldingSection fs, TextLine text, int documentLength) : base(text, documentLength) + { + this.fs = fs; + } + + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + return new FoldingLineTextRun(this, this.TextRunProperties) { textBrush = textBrush }; + } + + protected internal override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left) { + fs.IsFolded = false; + e.Handled = true; + } else { + base.OnMouseDown(e); + } + } + } + + sealed class FoldingLineTextRun : FormattedTextRun + { + internal Brush textBrush; + + public FoldingLineTextRun(FormattedTextElement element, TextRunProperties properties) + : base(element, properties) + { + } + + public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways) + { + var metrics = Format(double.PositiveInfinity); + Rect r = new Rect(origin.X, origin.Y - metrics.Baseline, metrics.Width, metrics.Height); + drawingContext.DrawRectangle(null, new Pen(textBrush, 1), r); + base.Draw(drawingContext, origin, rightToLeft, sideways); + } + } + + /// + /// Default brush for folding element text. Value: Brushes.Gray + /// + public static readonly Brush DefaultTextBrush = Brushes.Gray; + + static Brush textBrush = DefaultTextBrush; + + /// + /// Gets/sets the brush used for folding element text. + /// + public static Brush TextBrush { + get { return textBrush; } + set { textBrush = value; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs new file mode 100644 index 000000000..4035928b4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingManager.cs @@ -0,0 +1,388 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// Stores a list of foldings for a specific TextView and TextDocument. + /// + public class FoldingManager : IWeakEventListener + { + internal readonly TextDocument document; + + internal readonly List textViews = new List(); + readonly TextSegmentCollection foldings; + + #region Constructor + /// + /// Creates a new FoldingManager instance. + /// + public FoldingManager(TextDocument document) + { + if (document == null) + throw new ArgumentNullException("document"); + this.document = document; + this.foldings = new TextSegmentCollection(); + document.VerifyAccess(); + TextDocumentWeakEventManager.Changed.AddListener(document, this); + } + + /// + /// Creates a new FoldingManager instance. + /// + [Obsolete("Use the (TextDocument) constructor instead.")] + public FoldingManager(TextView textView, TextDocument document) + : this(document) + { + } + #endregion + + #region ReceiveWeakEvent + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextDocumentWeakEventManager.Changed)) { + OnDocumentChanged((DocumentChangeEventArgs)e); + return true; + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + + void OnDocumentChanged(DocumentChangeEventArgs e) + { + foldings.UpdateOffsets(e); + int newEndOffset = e.Offset + e.InsertionLength; + // extend end offset to the end of the line (including delimiter) + var endLine = document.GetLineByOffset(newEndOffset); + newEndOffset = endLine.Offset + endLine.TotalLength; + foreach (var affectedFolding in foldings.FindOverlappingSegments(e.Offset, newEndOffset - e.Offset)) { + if (affectedFolding.Length == 0) { + RemoveFolding(affectedFolding); + } else { + affectedFolding.ValidateCollapsedLineSections(); + } + } + } + #endregion + + #region Manage TextViews + internal void AddToTextView(TextView textView) + { + if (textView == null || textViews.Contains(textView)) + throw new ArgumentException(); + textViews.Add(textView); + foreach (FoldingSection fs in foldings) { + if (fs.collapsedSections != null) { + Array.Resize(ref fs.collapsedSections, textViews.Count); + fs.ValidateCollapsedLineSections(); + } + } + } + + internal void RemoveFromTextView(TextView textView) + { + int pos = textViews.IndexOf(textView); + if (pos < 0) + throw new ArgumentException(); + textViews.RemoveAt(pos); + foreach (FoldingSection fs in foldings) { + if (fs.collapsedSections != null) { + var c = new CollapsedLineSection[textViews.Count]; + Array.Copy(fs.collapsedSections, 0, c, 0, pos); + fs.collapsedSections[pos].Uncollapse(); + Array.Copy(fs.collapsedSections, pos + 1, c, pos, c.Length - pos); + fs.collapsedSections = c; + } + } + } + + internal void Redraw() + { + foreach (TextView textView in textViews) + textView.Redraw(); + } + + internal void Redraw(FoldingSection fs) + { + foreach (TextView textView in textViews) + textView.Redraw(fs); + } + #endregion + + #region Create / Remove / Clear + /// + /// Creates a folding for the specified text section. + /// + public FoldingSection CreateFolding(int startOffset, int endOffset) + { + if (startOffset >= endOffset) + throw new ArgumentException("startOffset must be less than endOffset"); + if (startOffset < 0 || endOffset > document.TextLength) + throw new ArgumentException("Folding must be within document boundary"); + FoldingSection fs = new FoldingSection(this, startOffset, endOffset); + foldings.Add(fs); + Redraw(fs); + return fs; + } + + /// + /// Removes a folding section from this manager. + /// + public void RemoveFolding(FoldingSection fs) + { + if (fs == null) + throw new ArgumentNullException("fs"); + fs.IsFolded = false; + foldings.Remove(fs); + Redraw(fs); + } + + /// + /// Removes all folding sections. + /// + public void Clear() + { + document.VerifyAccess(); + foreach (FoldingSection s in foldings) + s.IsFolded = false; + foldings.Clear(); + Redraw(); + } + #endregion + + #region Get...Folding + /// + /// Gets all foldings in this manager. + /// The foldings are returned sorted by start offset; + /// for multiple foldings at the same offset the order is undefined. + /// + public IEnumerable AllFoldings { + get { return foldings; } + } + + /// + /// Gets the first offset greater or equal to where a folded folding starts. + /// Returns -1 if there are no foldings after . + /// + public int GetNextFoldedFoldingStart(int startOffset) + { + FoldingSection fs = foldings.FindFirstSegmentWithStartAfter(startOffset); + while (fs != null && !fs.IsFolded) + fs = foldings.GetNextSegment(fs); + return fs != null ? fs.StartOffset : -1; + } + + /// + /// Gets the first folding with a greater or equal to + /// . + /// Returns null if there are no foldings after . + /// + public FoldingSection GetNextFolding(int startOffset) + { + // TODO: returns the longest folding instead of any folding at the first position after startOffset + return foldings.FindFirstSegmentWithStartAfter(startOffset); + } + + /// + /// Gets all foldings that start exactly at . + /// + public ReadOnlyCollection GetFoldingsAt(int startOffset) + { + List result = new List(); + FoldingSection fs = foldings.FindFirstSegmentWithStartAfter(startOffset); + while (fs != null && fs.StartOffset == startOffset) { + result.Add(fs); + fs = foldings.GetNextSegment(fs); + } + return result.AsReadOnly(); + } + + /// + /// Gets all foldings that contain . + /// + public ReadOnlyCollection GetFoldingsContaining(int offset) + { + return foldings.FindSegmentsContaining(offset); + } + #endregion + + #region UpdateFoldings + /// + /// Updates the foldings in this using the given new foldings. + /// This method will try to detect which new foldings correspond to which existing foldings; and will keep the state + /// () for existing foldings. + /// + /// The new set of foldings. These must be sorted by starting offset. + /// The first position of a parse error. Existing foldings starting after + /// this offset will be kept even if they don't appear in . + /// Use -1 for this parameter if there were no parse errors. + public void UpdateFoldings(IEnumerable newFoldings, int firstErrorOffset) + { + if (newFoldings == null) + throw new ArgumentNullException("newFoldings"); + + if (firstErrorOffset < 0) + firstErrorOffset = int.MaxValue; + + var oldFoldings = this.AllFoldings.ToArray(); + int oldFoldingIndex = 0; + int previousStartOffset = 0; + // merge new foldings into old foldings so that sections keep being collapsed + // both oldFoldings and newFoldings are sorted by start offset + foreach (NewFolding newFolding in newFoldings) { + // ensure newFoldings are sorted correctly + if (newFolding.StartOffset < previousStartOffset) + throw new ArgumentException("newFoldings must be sorted by start offset"); + previousStartOffset = newFolding.StartOffset; + + int startOffset = newFolding.StartOffset.CoerceValue(0, document.TextLength); + int endOffset = newFolding.EndOffset.CoerceValue(0, document.TextLength); + + if (newFolding.StartOffset == newFolding.EndOffset) + continue; // ignore zero-length foldings + + // remove old foldings that were skipped + while (oldFoldingIndex < oldFoldings.Length && newFolding.StartOffset > oldFoldings[oldFoldingIndex].StartOffset) { + this.RemoveFolding(oldFoldings[oldFoldingIndex++]); + } + FoldingSection section; + // reuse current folding if its matching: + if (oldFoldingIndex < oldFoldings.Length && newFolding.StartOffset == oldFoldings[oldFoldingIndex].StartOffset) { + section = oldFoldings[oldFoldingIndex++]; + section.Length = newFolding.EndOffset - newFolding.StartOffset; + } else { + // no matching current folding; create a new one: + section = this.CreateFolding(newFolding.StartOffset, newFolding.EndOffset); + // auto-close #regions only when opening the document + section.IsFolded = newFolding.DefaultClosed; + section.Tag = newFolding; + } + section.Title = newFolding.Name; + } + // remove all outstanding old foldings: + while (oldFoldingIndex < oldFoldings.Length) { + FoldingSection oldSection = oldFoldings[oldFoldingIndex++]; + if (oldSection.StartOffset >= firstErrorOffset) + break; + this.RemoveFolding(oldSection); + } + } + #endregion + + #region Install + /// + /// Adds Folding support to the specified text area. + /// Warning: The folding manager is only valid for the text area's current document. The folding manager + /// must be uninstalled before the text area is bound to a different document. + /// + /// The that manages the list of foldings inside the text area. + public static FoldingManager Install(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + return new FoldingManagerInstallation(textArea); + } + + /// + /// Uninstalls the folding manager. + /// + /// The specified manager was not created using . + public static void Uninstall(FoldingManager manager) + { + if (manager == null) + throw new ArgumentNullException("manager"); + FoldingManagerInstallation installation = manager as FoldingManagerInstallation; + if (installation != null) { + installation.Uninstall(); + } else { + throw new ArgumentException("FoldingManager was not created using FoldingManager.Install"); + } + } + + sealed class FoldingManagerInstallation : FoldingManager + { + TextArea textArea; + FoldingMargin margin; + FoldingElementGenerator generator; + + public FoldingManagerInstallation(TextArea textArea) : base(textArea.Document) + { + this.textArea = textArea; + margin = new FoldingMargin() { FoldingManager = this }; + generator = new FoldingElementGenerator() { FoldingManager = this }; + textArea.LeftMargins.Add(margin); + textArea.TextView.Services.AddService(typeof(FoldingManager), this); + // HACK: folding only works correctly when it has highest priority + textArea.TextView.ElementGenerators.Insert(0, generator); + textArea.Caret.PositionChanged += textArea_Caret_PositionChanged; + } + + /* + void DemoMode() + { + foldingGenerator = new FoldingElementGenerator() { FoldingManager = fm }; + foldingMargin = new FoldingMargin { FoldingManager = fm }; + foldingMarginBorder = new Border { + Child = foldingMargin, + Background = new LinearGradientBrush(Colors.White, Colors.Transparent, 0) + }; + foldingMarginBorder.SizeChanged += UpdateTextViewClip; + textEditor.TextArea.TextView.ElementGenerators.Add(foldingGenerator); + textEditor.TextArea.LeftMargins.Add(foldingMarginBorder); + } + + void UpdateTextViewClip(object sender, SizeChangedEventArgs e) + { + textEditor.TextArea.TextView.Clip = new RectangleGeometry( + new Rect(-foldingMarginBorder.ActualWidth, + 0, + textEditor.TextArea.TextView.ActualWidth + foldingMarginBorder.ActualWidth, + textEditor.TextArea.TextView.ActualHeight)); + } + */ + + public void Uninstall() + { + Clear(); + if (textArea != null) { + textArea.Caret.PositionChanged -= textArea_Caret_PositionChanged; + textArea.LeftMargins.Remove(margin); + textArea.TextView.ElementGenerators.Remove(generator); + textArea.TextView.Services.RemoveService(typeof(FoldingManager)); + margin = null; + generator = null; + textArea = null; + } + } + + void textArea_Caret_PositionChanged(object sender, EventArgs e) + { + // Expand Foldings when Caret is moved into them. + int caretOffset = textArea.Caret.Offset; + foreach (FoldingSection s in GetFoldingsContaining(caretOffset)) { + if (s.IsFolded && s.StartOffset < caretOffset && caretOffset < s.EndOffset) { + s.IsFolded = false; + } + } + } + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs new file mode 100644 index 000000000..c3d03646c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMargin.cs @@ -0,0 +1,343 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// A margin that shows markers for foldings and allows to expand/collapse the foldings. + /// + public class FoldingMargin : AbstractMargin + { + /// + /// Gets/Sets the folding manager from which the foldings should be shown. + /// + public FoldingManager FoldingManager { get; set; } + + internal const double SizeFactor = Constants.PixelPerPoint; + + #region Brushes + /// + /// FoldingMarkerBrush dependency property. + /// + public static readonly DependencyProperty FoldingMarkerBrushProperty = + DependencyProperty.RegisterAttached("FoldingMarkerBrush", typeof(Brush), typeof(FoldingMargin), + new FrameworkPropertyMetadata(Brushes.Gainsboro)); + + /// + /// Gets/sets the Brush used for displaying the lines of folding markers. + /// + public Brush FoldingMarkerBrush { + get { return (Brush)GetValue(FoldingMarkerBrushProperty); } + set { SetValue(FoldingMarkerBrushProperty, value); } + } + + /// + /// FoldingMarkerBackgroundBrush dependency property. + /// + public static readonly DependencyProperty FoldingMarkerBackgroundBrushProperty = + DependencyProperty.RegisterAttached("FoldingMarkerBackgroundBrush", typeof(Brush), typeof(FoldingMargin), + new FrameworkPropertyMetadata(Brushes.Transparent)); + + /// + /// Gets/sets the Brush used for displaying the background of folding markers. + /// + public Brush FoldingMarkerBackgroundBrush { + get { return (Brush)GetValue(FoldingMarkerBackgroundBrushProperty); } + set { SetValue(FoldingMarkerBackgroundBrushProperty, value); } + } + + /// + /// SelectedFoldingMarkerBrush dependency property. + /// + public static readonly DependencyProperty SelectedFoldingMarkerBrushProperty = + DependencyProperty.RegisterAttached("SelectedFoldingMarkerBrush", + typeof(Brush), typeof(FoldingMargin), + new FrameworkPropertyMetadata(Brushes.Gainsboro)); + + /// + /// Gets/sets the Brush used for displaying the lines of selected folding markers. + /// + public Brush SelectedFoldingMarkerBrush { + get { return (Brush)GetValue(SelectedFoldingMarkerBrushProperty); } + set { SetValue(SelectedFoldingMarkerBrushProperty, value); } + } + + /// + /// SelectedFoldingMarkerBackgroundBrush dependency property. + /// + public static readonly DependencyProperty SelectedFoldingMarkerBackgroundBrushProperty = + DependencyProperty.RegisterAttached("SelectedFoldingMarkerBackgroundBrush", + typeof(Brush), typeof(FoldingMargin), + new FrameworkPropertyMetadata(Brushes.Transparent)); + + /// + /// Gets/sets the Brush used for displaying the background of selected folding markers. + /// + public Brush SelectedFoldingMarkerBackgroundBrush { + get { return (Brush)GetValue(SelectedFoldingMarkerBackgroundBrushProperty); } + set { SetValue(SelectedFoldingMarkerBackgroundBrushProperty, value); } + } + + static void OnUpdateBrushes(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + FoldingMargin m = null; + if (d is FoldingMargin) + m = (FoldingMargin)d; + else if (d is TextEditor) + m = ((TextEditor)d).TextArea.LeftMargins.FirstOrDefault(c => c is FoldingMargin) as FoldingMargin; + if (m == null) return; + if (e.Property.Name == FoldingMarkerBrushProperty.Name) + m.foldingControlPen = MakeFrozenPen((Brush)e.NewValue); + if (e.Property.Name == SelectedFoldingMarkerBrushProperty.Name) + m.selectedFoldingControlPen = MakeFrozenPen((Brush)e.NewValue); + } + #endregion + + /// + protected override Size MeasureOverride(Size availableSize) + { + foreach (FoldingMarginMarker m in markers) { + m.Measure(availableSize); + } + double width = SizeFactor * (double)GetValue(TextBlock.FontSizeProperty); + return new Size(PixelSnapHelpers.RoundToOdd(width, PixelSnapHelpers.GetPixelSize(this).Width), 0); + } + + /// + protected override Size ArrangeOverride(Size finalSize) + { + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); + foreach (FoldingMarginMarker m in markers) { + int visualColumn = m.VisualLine.GetVisualColumn(m.FoldingSection.StartOffset - m.VisualLine.FirstDocumentLine.Offset); + TextLine textLine = m.VisualLine.GetTextLine(visualColumn); + double yPos = m.VisualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextMiddle) - TextView.VerticalOffset; + yPos -= m.DesiredSize.Height / 2; + double xPos = (finalSize.Width - m.DesiredSize.Width) / 2; + m.Arrange(new Rect(PixelSnapHelpers.Round(new Point(xPos, yPos), pixelSize), m.DesiredSize)); + } + return base.ArrangeOverride(finalSize); + } + + /// + protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) + { + if (oldTextView != null) { + oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged; + } + base.OnTextViewChanged(oldTextView, newTextView); + if (newTextView != null) { + newTextView.VisualLinesChanged += TextViewVisualLinesChanged; + } + TextViewVisualLinesChanged(null, null); + } + + List markers = new List(); + + void TextViewVisualLinesChanged(object sender, EventArgs e) + { + foreach (FoldingMarginMarker m in markers) { + RemoveVisualChild(m); + } + markers.Clear(); + InvalidateVisual(); + if (TextView != null && FoldingManager != null && TextView.VisualLinesValid) { + foreach (VisualLine line in TextView.VisualLines) { + FoldingSection fs = FoldingManager.GetNextFolding(line.FirstDocumentLine.Offset); + if (fs == null) + continue; + if (fs.StartOffset <= line.LastDocumentLine.Offset + line.LastDocumentLine.Length) { + FoldingMarginMarker m = new FoldingMarginMarker { + IsExpanded = !fs.IsFolded, + VisualLine = line, + FoldingSection = fs + }; + + markers.Add(m); + AddVisualChild(m); + + m.IsMouseDirectlyOverChanged += delegate { InvalidateVisual(); }; + + InvalidateMeasure(); + continue; + } + } + } + } + + /// + protected override int VisualChildrenCount { + get { return markers.Count; } + } + + /// + protected override Visual GetVisualChild(int index) + { + return markers[index]; + } + + Pen foldingControlPen = MakeFrozenPen((Brush)FoldingMarkerBrushProperty.DefaultMetadata.DefaultValue); + Pen selectedFoldingControlPen = MakeFrozenPen((Brush)SelectedFoldingMarkerBrushProperty.DefaultMetadata.DefaultValue); + + static Pen MakeFrozenPen(Brush brush) + { + Pen pen = new Pen(brush, 1); + pen.Freeze(); + return pen; + } + + /// + protected override void OnRender(DrawingContext drawingContext) + { + if (TextView == null || !TextView.VisualLinesValid) + return; + if (TextView.VisualLines.Count == 0 || FoldingManager == null) + return; + + var allTextLines = TextView.VisualLines.SelectMany(vl => vl.TextLines).ToList(); + Pen[] colors = new Pen[allTextLines.Count + 1]; + Pen[] endMarker = new Pen[allTextLines.Count]; + + CalculateFoldLinesForFoldingsActiveAtStart(allTextLines, colors, endMarker); + CalculateFoldLinesForMarkers(allTextLines, colors, endMarker); + DrawFoldLines(drawingContext, colors, endMarker); + + base.OnRender(drawingContext); + } + + /// + /// Calculates fold lines for all folding sections that start in front of the current view + /// and run into the current view. + /// + void CalculateFoldLinesForFoldingsActiveAtStart(List allTextLines, Pen[] colors, Pen[] endMarker) + { + int viewStartOffset = TextView.VisualLines[0].FirstDocumentLine.Offset; + int viewEndOffset = TextView.VisualLines.Last().LastDocumentLine.EndOffset; + var foldings = FoldingManager.GetFoldingsContaining(viewStartOffset); + int maxEndOffset = 0; + foreach (FoldingSection fs in foldings) { + int end = fs.EndOffset; + if (end <= viewEndOffset && !fs.IsFolded) { + int textLineNr = GetTextLineIndexFromOffset(allTextLines, end); + if (textLineNr >= 0) { + endMarker[textLineNr] = foldingControlPen; + } + } + if (end > maxEndOffset && fs.StartOffset < viewStartOffset) { + maxEndOffset = end; + } + } + if (maxEndOffset > 0) { + if (maxEndOffset > viewEndOffset) { + for (int i = 0; i < colors.Length; i++) { + colors[i] = foldingControlPen; + } + } else { + int maxTextLine = GetTextLineIndexFromOffset(allTextLines, maxEndOffset); + for (int i = 0; i <= maxTextLine; i++) { + colors[i] = foldingControlPen; + } + } + } + } + + /// + /// Calculates fold lines for all folding sections that start inside the current view + /// + void CalculateFoldLinesForMarkers(List allTextLines, Pen[] colors, Pen[] endMarker) + { + foreach (FoldingMarginMarker marker in markers) { + int end = marker.FoldingSection.EndOffset; + int endTextLineNr = GetTextLineIndexFromOffset(allTextLines, end); + if (!marker.FoldingSection.IsFolded && endTextLineNr >= 0) { + if (marker.IsMouseDirectlyOver) + endMarker[endTextLineNr] = selectedFoldingControlPen; + else if (endMarker[endTextLineNr] == null) + endMarker[endTextLineNr] = foldingControlPen; + } + int startTextLineNr = GetTextLineIndexFromOffset(allTextLines, marker.FoldingSection.StartOffset); + if (startTextLineNr >= 0) { + for (int i = startTextLineNr + 1; i < colors.Length && i - 1 != endTextLineNr; i++) { + if (marker.IsMouseDirectlyOver) + colors[i] = selectedFoldingControlPen; + else if (colors[i] == null) + colors[i] = foldingControlPen; + } + } + } + } + + /// + /// Draws the lines for the folding sections (vertical line with 'color', horizontal lines with 'endMarker') + /// Each entry in the input arrays corresponds to one TextLine. + /// + void DrawFoldLines(DrawingContext drawingContext, Pen[] colors, Pen[] endMarker) + { + // Because we are using PenLineCap.Flat (the default), for vertical lines, + // Y coordinates must be on pixel boundaries, whereas the X coordinate must be in the + // middle of a pixel. (and the other way round for horizontal lines) + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); + double markerXPos = PixelSnapHelpers.PixelAlign(RenderSize.Width / 2, pixelSize.Width); + double startY = 0; + Pen currentPen = colors[0]; + + Pen p = new Pen(FoldingMarkerBrush, 1); + + int tlNumber = 0; + foreach (VisualLine vl in TextView.VisualLines) { + foreach (TextLine tl in vl.TextLines) { + if (endMarker[tlNumber] != null) { + double visualPos = GetVisualPos(vl, tl, pixelSize.Height); + drawingContext.DrawLine(p, new Point(markerXPos - pixelSize.Width / 2, visualPos), new Point(RenderSize.Width - 4, visualPos)); + } + if (colors[tlNumber + 1] != currentPen) { + double visualPos = GetVisualPos(vl, tl, pixelSize.Height); + if (currentPen != null) { + drawingContext.DrawLine(p, new Point(markerXPos, startY + pixelSize.Height), new Point(markerXPos, visualPos - pixelSize.Height / 2)); + } + currentPen = colors[tlNumber + 1]; + startY = visualPos; + } + tlNumber++; + } + } + if (currentPen != null) { + drawingContext.DrawLine(currentPen, new Point(markerXPos, startY + pixelSize.Height / 2), new Point(markerXPos, RenderSize.Height)); + } + } + + double GetVisualPos(VisualLine vl, TextLine tl, double pixelHeight) + { + double pos = vl.GetTextLineVisualYPosition(tl, VisualYPosition.TextMiddle) - TextView.VerticalOffset; + return PixelSnapHelpers.PixelAlign(pos, pixelHeight); + } + + int GetTextLineIndexFromOffset(List textLines, int offset) + { + int lineNumber = TextView.Document.GetLineByOffset(offset).LineNumber; + VisualLine vl = TextView.GetVisualLine(lineNumber); + if (vl != null) { + int relOffset = offset - vl.FirstDocumentLine.Offset; + TextLine line = vl.GetTextLine(vl.GetVisualColumn(relOffset)); + return textLines.IndexOf(line); + } + return -1; + } + + static FoldingMargin() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(FoldingMargin), new FrameworkPropertyMetadata(typeof(FoldingMargin))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs new file mode 100644 index 000000000..5b69d7485 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingMarginMarker.cs @@ -0,0 +1,90 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Folding +{ + sealed class FoldingMarginMarker : UIElement + { + internal VisualLine VisualLine; + internal FoldingSection FoldingSection; + + bool isExpanded; + + public bool IsExpanded { + get { return isExpanded; } + set { + if (isExpanded != value) { + isExpanded = value; + InvalidateVisual(); + } + if (FoldingSection != null) + FoldingSection.IsFolded = !value; + } + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + if (!e.Handled) { + if (e.ChangedButton == MouseButton.Left) { + IsExpanded = !IsExpanded; + e.Handled = true; + } + } + } + + const double MarginSizeFactor = 0.7; + + protected override Size MeasureCore(Size availableSize) + { + double size = MarginSizeFactor * FoldingMargin.SizeFactor * (double)GetValue(TextBlock.FontSizeProperty); + size = PixelSnapHelpers.RoundToOdd(size, PixelSnapHelpers.GetPixelSize(this).Width); + return new Size(size, size); + } + + protected override void OnRender(DrawingContext drawingContext) + { + FoldingMargin margin = VisualParent as FoldingMargin; + Pen activePen = new Pen(margin.SelectedFoldingMarkerBrush, 1); + + Pen inactivePen = new Pen(margin.FoldingMarkerBrush, 1); + activePen.StartLineCap = inactivePen.StartLineCap = PenLineCap.Square; + activePen.EndLineCap = inactivePen.EndLineCap = PenLineCap.Square; + Size pixelSize = PixelSnapHelpers.GetPixelSize(this); + Rect rect = new Rect(pixelSize.Width / 2, + pixelSize.Height / 2, + this.RenderSize.Width - pixelSize.Width - 2, + this.RenderSize.Height - pixelSize.Height - 2); + + drawingContext.DrawRectangle( + IsMouseDirectlyOver ? margin.FoldingMarkerBackgroundBrush : margin.FoldingMarkerBackgroundBrush, + IsMouseDirectlyOver ? activePen : inactivePen, rect); + double middleX = rect.Left + rect.Width / 2; + double middleY = rect.Top + rect.Height / 2; + double space = PixelSnapHelpers.Round(rect.Width / 8, pixelSize.Width) + pixelSize.Width; + drawingContext.DrawLine(activePen, + new Point(rect.Left + space, middleY), + new Point(rect.Right - space, middleY)); + if (!isExpanded) { + drawingContext.DrawLine(activePen, + new Point(middleX, rect.Top + space), + new Point(middleX, rect.Bottom - space)); + } + } + + protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) + { + base.OnIsMouseDirectlyOverChanged(e); + InvalidateVisual(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs new file mode 100644 index 000000000..fd27e9b3b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingSection.cs @@ -0,0 +1,186 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Text; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// A section that can be folded. + /// + public sealed class FoldingSection : TextSegment + { + readonly FoldingManager manager; + bool isFolded; + internal CollapsedLineSection[] collapsedSections; + string title; + + /// + /// Gets/sets if the section is folded. + /// + public bool IsFolded { + get { return isFolded; } + set { + if (isFolded != value) { + isFolded = value; + ValidateCollapsedLineSections(); // create/destroy CollapsedLineSection + manager.Redraw(this); + } + } + } + + internal void ValidateCollapsedLineSections() + { + if (!isFolded) { + RemoveCollapsedLineSection(); + return; + } + // It is possible that StartOffset/EndOffset get set to invalid values via the property setters in TextSegment, + // so we coerce those values into the valid range. + DocumentLine startLine = manager.document.GetLineByOffset(StartOffset.CoerceValue(0, manager.document.TextLength)); + DocumentLine endLine = manager.document.GetLineByOffset(EndOffset.CoerceValue(0, manager.document.TextLength)); + if (startLine == endLine) { + RemoveCollapsedLineSection(); + } else { + if (collapsedSections == null) + collapsedSections = new CollapsedLineSection[manager.textViews.Count]; + // Validate collapsed line sections + DocumentLine startLinePlusOne = startLine.NextLine; + for (int i = 0; i < collapsedSections.Length; i++) { + var collapsedSection = collapsedSections[i]; + if (collapsedSection == null || collapsedSection.Start != startLinePlusOne || collapsedSection.End != endLine) { + // recreate this collapsed section + if (collapsedSection != null) { + Debug.WriteLine("CollapsedLineSection validation - recreate collapsed section from " + startLinePlusOne + " to " + endLine); + collapsedSection.Uncollapse(); + } + collapsedSections[i] = manager.textViews[i].CollapseLines(startLinePlusOne, endLine); + } + } + } + } + + /// + protected override void OnSegmentChanged() + { + ValidateCollapsedLineSections(); + base.OnSegmentChanged(); + // don't redraw if the FoldingSection wasn't added to the FoldingManager's collection yet + if (IsConnectedToCollection) + manager.Redraw(this); + } + + /// + /// Gets/Sets the text used to display the collapsed version of the folding section. + /// + public string Title { + get { + return title; + } + set { + if (title != value) { + title = value; + if (this.IsFolded) + manager.Redraw(this); + } + } + } + + /// + /// Gets the content of the collapsed lines as text. + /// + public string TextContent { + get { + return manager.document.GetText(StartOffset, EndOffset - StartOffset); + } + } + + /// + /// Gets the content of the collapsed lines as tooltip text. + /// + [Obsolete] + public string TooltipText { + get { + // This fixes SD-1394: + // Each line is checked for leading indentation whitespaces. If + // a line has the same or more indentation than the first line, + // it is reduced. If a line is less indented than the first line + // the indentation is removed completely. + // + // See the following example: + // line 1 + // line 2 + // line 3 + // line 4 + // + // is reduced to: + // line 1 + // line 2 + // line 3 + // line 4 + + var startLine = manager.document.GetLineByOffset(StartOffset); + var endLine = manager.document.GetLineByOffset(EndOffset); + var builder = new StringBuilder(); + + var current = startLine; + ISegment startIndent = TextUtilities.GetLeadingWhitespace(manager.document, startLine); + + while (current != endLine.NextLine) { + ISegment currentIndent = TextUtilities.GetLeadingWhitespace(manager.document, current); + + if (current == startLine && current == endLine) + builder.Append(manager.document.GetText(StartOffset, EndOffset - StartOffset)); + else if (current == startLine) { + if (current.EndOffset - StartOffset > 0) + builder.AppendLine(manager.document.GetText(StartOffset, current.EndOffset - StartOffset).TrimStart()); + } else if (current == endLine) { + if (startIndent.Length <= currentIndent.Length) + builder.Append(manager.document.GetText(current.Offset + startIndent.Length, EndOffset - current.Offset - startIndent.Length)); + else + builder.Append(manager.document.GetText(current.Offset + currentIndent.Length, EndOffset - current.Offset - currentIndent.Length)); + } else { + if (startIndent.Length <= currentIndent.Length) + builder.AppendLine(manager.document.GetText(current.Offset + startIndent.Length, current.Length - startIndent.Length)); + else + builder.AppendLine(manager.document.GetText(current.Offset + currentIndent.Length, current.Length - currentIndent.Length)); + } + + current = current.NextLine; + } + + return builder.ToString(); + } + } + + /// + /// Gets/Sets an additional object associated with this folding section. + /// + public object Tag { get; set; } + + internal FoldingSection(FoldingManager manager, int startOffset, int endOffset) + { + Debug.Assert(manager != null); + this.manager = manager; + this.StartOffset = startOffset; + this.Length = endOffset - startOffset; + } + + void RemoveCollapsedLineSection() + { + if (collapsedSections != null) { + foreach (var collapsedSection in collapsedSections) { + if (collapsedSection != null && collapsedSection.Start != null) + collapsedSection.Uncollapse(); + } + collapsedSections = null; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs new file mode 100644 index 000000000..7f397c82e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/NewFolding.cs @@ -0,0 +1,70 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// Helper class used for . + /// + public class NewFolding : ISegment + { + /// + /// Gets/Sets the start offset. + /// + public int StartOffset { get; set; } + + /// + /// Gets/Sets the end offset. + /// + public int EndOffset { get; set; } + + /// + /// Gets/Sets the name displayed for the folding. + /// + public string Name { get; set; } + + /// + /// Gets/Sets whether the folding is closed by default. + /// + public bool DefaultClosed { get; set; } + + /// + /// Creates a new NewFolding instance. + /// + public NewFolding() + { + } + + /// + /// Creates a new NewFolding instance. + /// + public NewFolding(int start, int end) + { + if (!(start <= end)) + throw new ArgumentException("'start' must be less than 'end'"); + this.StartOffset = start; + this.EndOffset = end; + this.Name = null; + this.DefaultClosed = false; + } + + int ISegment.Offset { + get { return this.StartOffset; } + } + + int ISegment.Length { + get { return this.EndOffset - this.StartOffset; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs new file mode 100644 index 000000000..0f888a572 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/XmlFoldingStrategy.cs @@ -0,0 +1,215 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Folding +{ + /// + /// Holds information about the start of a fold in an xml string. + /// + sealed class XmlFoldStart : NewFolding + { + internal int StartLine; + } + + /// + /// Determines folds for an xml string in the editor. + /// + public class XmlFoldingStrategy : AbstractFoldingStrategy + { + /// + /// Flag indicating whether attributes should be displayed on folded + /// elements. + /// + public bool ShowAttributesWhenFolded { get; set; } + + /// + /// Create s for the specified document. + /// + public override IEnumerable CreateNewFoldings(TextDocument document, out int firstErrorOffset) + { + try { + XmlTextReader reader = new XmlTextReader(document.CreateReader()); + reader.XmlResolver = null; // don't resolve DTDs + return CreateNewFoldings(document, reader, out firstErrorOffset); + } catch (XmlException) { + firstErrorOffset = 0; + return Enumerable.Empty(); + } + } + + /// + /// Create s for the specified document. + /// + public IEnumerable CreateNewFoldings(TextDocument document, XmlReader reader, out int firstErrorOffset) + { + Stack stack = new Stack(); + List foldMarkers = new List(); + try { + while (reader.Read()) { + switch (reader.NodeType) { + case XmlNodeType.Element: + if (!reader.IsEmptyElement) { + XmlFoldStart newFoldStart = CreateElementFoldStart(document, reader); + stack.Push(newFoldStart); + } + break; + + case XmlNodeType.EndElement: + XmlFoldStart foldStart = stack.Pop(); + CreateElementFold(document, foldMarkers, reader, foldStart); + break; + + case XmlNodeType.Comment: + CreateCommentFold(document, foldMarkers, reader); + break; + } + } + firstErrorOffset = -1; + } catch (XmlException ex) { + // ignore errors at invalid positions (prevent ArgumentOutOfRangeException) + if (ex.LineNumber >= 1 && ex.LineNumber <= document.LineCount) + firstErrorOffset = document.GetOffset(ex.LineNumber, ex.LinePosition); + else + firstErrorOffset = 0; + } + foldMarkers.Sort((a,b) => a.StartOffset.CompareTo(b.StartOffset)); + return foldMarkers; + } + + static int GetOffset(TextDocument document, XmlReader reader) + { + IXmlLineInfo info = reader as IXmlLineInfo; + if (info != null && info.HasLineInfo()) { + return document.GetOffset(info.LineNumber, info.LinePosition); + } else { + throw new ArgumentException("XmlReader does not have positioning information."); + } + } + + /// + /// Creates a comment fold if the comment spans more than one line. + /// + /// The text displayed when the comment is folded is the first + /// line of the comment. + static void CreateCommentFold(TextDocument document, List foldMarkers, XmlReader reader) + { + string comment = reader.Value; + if (comment != null) { + int firstNewLine = comment.IndexOf('\n'); + if (firstNewLine >= 0) { + + // Take off 4 chars to get the actual comment start (takes + // into account the "); + foldMarkers.Add(new NewFolding(startOffset, endOffset) { Name = foldText } ); + } + } + } + + /// + /// Creates an XmlFoldStart for the start tag of an element. + /// + XmlFoldStart CreateElementFoldStart(TextDocument document, XmlReader reader) + { + // Take off 1 from the offset returned + // from the xml since it points to the start + // of the element name and not the beginning + // tag. + //XmlFoldStart newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2); + XmlFoldStart newFoldStart = new XmlFoldStart(); + + IXmlLineInfo lineInfo = (IXmlLineInfo)reader; + newFoldStart.StartLine = lineInfo.LineNumber; + newFoldStart.StartOffset = document.GetOffset(newFoldStart.StartLine, lineInfo.LinePosition - 1); + + if (this.ShowAttributesWhenFolded && reader.HasAttributes) { + newFoldStart.Name = String.Concat("<", reader.Name, " ", GetAttributeFoldText(reader), ">"); + } else { + newFoldStart.Name = String.Concat("<", reader.Name, ">"); + } + + return newFoldStart; + } + + /// + /// Create an element fold if the start and end tag are on + /// different lines. + /// + static void CreateElementFold(TextDocument document, List foldMarkers, XmlReader reader, XmlFoldStart foldStart) + { + IXmlLineInfo lineInfo = (IXmlLineInfo)reader; + int endLine = lineInfo.LineNumber; + if (endLine > foldStart.StartLine) { + int endCol = lineInfo.LinePosition + reader.Name.Length + 1; + foldStart.EndOffset = document.GetOffset(endLine, endCol); + foldMarkers.Add(foldStart); + } + } + + /// + /// Gets the element's attributes as a string on one line that will + /// be displayed when the element is folded. + /// + /// + /// Currently this puts all attributes from an element on the same + /// line of the start tag. It does not cater for elements where attributes + /// are not on the same line as the start tag. + /// + static string GetAttributeFoldText(XmlReader reader) + { + StringBuilder text = new StringBuilder(); + + for (int i = 0; i < reader.AttributeCount; ++i) { + reader.MoveToAttribute(i); + + text.Append(reader.Name); + text.Append("="); + text.Append(reader.QuoteChar.ToString()); + text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar)); + text.Append(reader.QuoteChar.ToString()); + + // Append a space if this is not the + // last attribute. + if (i < reader.AttributeCount - 1) { + text.Append(" "); + } + } + + return text.ToString(); + } + + /// + /// Xml encode the attribute string since the string returned from + /// the XmlTextReader is the plain unencoded string and .NET + /// does not provide us with an xml encode method. + /// + static string XmlEncodeAttributeValue(string attributeValue, char quoteChar) + { + StringBuilder encodedValue = new StringBuilder(attributeValue); + + encodedValue.Replace("&", "&"); + encodedValue.Replace("<", "<"); + encodedValue.Replace(">", ">"); + + if (quoteChar == '"') { + encodedValue.Replace("\"", """); + } else { + encodedValue.Replace("'", "'"); + } + + return encodedValue.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/DocumentHighlighter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/DocumentHighlighter.cs new file mode 100644 index 000000000..393c0a930 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/DocumentHighlighter.cs @@ -0,0 +1,468 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; +using SpanStack = Tango.Scripting.Editors.Utils.ImmutableStack; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// This class can syntax-highlight a document. + /// It automatically manages invalidating the highlighting when the document changes. + /// + public class DocumentHighlighter : ILineTracker, IHighlighter + { + /// + /// Stores the span state at the end of each line. + /// storedSpanStacks[0] = state at beginning of document + /// storedSpanStacks[i] = state after line i + /// + readonly CompressingTreeList storedSpanStacks = new CompressingTreeList(object.ReferenceEquals); + readonly CompressingTreeList isValid = new CompressingTreeList((a, b) => a == b); + readonly TextDocument document; + readonly HighlightingRuleSet baseRuleSet; + bool isHighlighting; + + /// + /// Gets the document that this DocumentHighlighter is highlighting. + /// + public TextDocument Document { + get { return document; } + } + + /// + /// Creates a new DocumentHighlighter instance. + /// + public DocumentHighlighter(TextDocument document, HighlightingRuleSet baseRuleSet) + { + if (document == null) + throw new ArgumentNullException("document"); + if (baseRuleSet == null) + throw new ArgumentNullException("baseRuleSet"); + this.document = document; + this.baseRuleSet = baseRuleSet; + WeakLineTracker.Register(document, this); + InvalidateHighlighting(); + } + + void ILineTracker.BeforeRemoveLine(DocumentLine line) + { + CheckIsHighlighting(); + int number = line.LineNumber; + storedSpanStacks.RemoveAt(number); + isValid.RemoveAt(number); + if (number < isValid.Count) { + isValid[number] = false; + if (number < firstInvalidLine) + firstInvalidLine = number; + } + } + + void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength) + { + CheckIsHighlighting(); + int number = line.LineNumber; + isValid[number] = false; + if (number < firstInvalidLine) + firstInvalidLine = number; + } + + void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) + { + CheckIsHighlighting(); + Debug.Assert(insertionPos.LineNumber + 1 == newLine.LineNumber); + int lineNumber = newLine.LineNumber; + storedSpanStacks.Insert(lineNumber, null); + isValid.Insert(lineNumber, false); + if (lineNumber < firstInvalidLine) + firstInvalidLine = lineNumber; + } + + void ILineTracker.RebuildDocument() + { + InvalidateHighlighting(); + } + + ImmutableStack initialSpanStack = SpanStack.Empty; + + /// + /// Gets/sets the the initial span stack of the document. Default value is . + /// + public ImmutableStack InitialSpanStack { + get { return initialSpanStack; } + set { + if (value == null) + initialSpanStack = SpanStack.Empty; + else + initialSpanStack = value; + InvalidateHighlighting(); + } + } + + /// + /// Invalidates all stored highlighting info. + /// When the document changes, the highlighting is invalidated automatically, this method + /// needs to be called only when there are changes to the highlighting rule set. + /// + public void InvalidateHighlighting() + { + CheckIsHighlighting(); + storedSpanStacks.Clear(); + storedSpanStacks.Add(initialSpanStack); + storedSpanStacks.InsertRange(1, document.LineCount, null); + isValid.Clear(); + isValid.Add(true); + isValid.InsertRange(1, document.LineCount, false); + firstInvalidLine = 1; + } + + int firstInvalidLine; + + /// + /// Highlights the specified document line. + /// + /// The line to highlight. + /// A line object that represents the highlighted sections. + [ObsoleteAttribute("Use the (int lineNumber) overload instead")] + public HighlightedLine HighlightLine(DocumentLine line) + { + if (!document.Lines.Contains(line)) + throw new ArgumentException("The specified line does not belong to the document."); + return HighlightLine(line.LineNumber); + } + + /// + public HighlightedLine HighlightLine(int lineNumber) + { + ThrowUtil.CheckInRangeInclusive(lineNumber, "lineNumber", 1, document.LineCount); + CheckIsHighlighting(); + isHighlighting = true; + try { + HighlightUpTo(lineNumber); + DocumentLine line = document.GetLineByNumber(lineNumber); + highlightedLine = new HighlightedLine(document, line); + HighlightLineAndUpdateTreeList(line, lineNumber); + return highlightedLine; + } finally { + highlightedLine = null; + isHighlighting = false; + } + } + + /// + public SpanStack GetSpanStack(int lineNumber) + { + ThrowUtil.CheckInRangeInclusive(lineNumber, "lineNumber", 0, document.LineCount); + if (firstInvalidLine <= lineNumber) { + CheckIsHighlighting(); + isHighlighting = true; + try { + HighlightUpTo(lineNumber + 1); + } finally { + isHighlighting = false; + } + } + return storedSpanStacks[lineNumber]; + } + + void CheckIsHighlighting() + { + if (isHighlighting) { + throw new InvalidOperationException("Invalid call - a highlighting operation is currently running."); + } + } + + void HighlightUpTo(int targetLineNumber) + { + Debug.Assert(highlightedLine == null); // ensure this method is only used for + while (firstInvalidLine < targetLineNumber) { + HighlightLineAndUpdateTreeList(document.GetLineByNumber(firstInvalidLine), firstInvalidLine); + } + } + + void HighlightLineAndUpdateTreeList(DocumentLine line, int lineNumber) + { + //Debug.WriteLine("Highlight line " + lineNumber + (highlightedLine != null ? "" : " (span stack only)")); + spanStack = storedSpanStacks[lineNumber - 1]; + HighlightLineInternal(line); + if (!EqualSpanStacks(spanStack, storedSpanStacks[lineNumber])) { + isValid[lineNumber] = true; + //Debug.WriteLine("Span stack in line " + lineNumber + " changed from " + storedSpanStacks[lineNumber] + " to " + spanStack); + storedSpanStacks[lineNumber] = spanStack; + if (lineNumber + 1 < isValid.Count) { + isValid[lineNumber + 1] = false; + firstInvalidLine = lineNumber + 1; + } else { + firstInvalidLine = int.MaxValue; + } + OnHighlightStateChanged(line, lineNumber); + } else if (firstInvalidLine == lineNumber) { + isValid[lineNumber] = true; + firstInvalidLine = isValid.IndexOf(false); + if (firstInvalidLine < 0) + firstInvalidLine = int.MaxValue; + } + } + + static bool EqualSpanStacks(SpanStack a, SpanStack b) + { + // We must use value equality between the stacks because TextViewDocumentHighlighter.OnHighlightStateChanged + // depends on the fact that equal input state + unchanged line contents produce equal output state. + if (a == b) + return true; + if (a == null || b == null) + return false; + while (!a.IsEmpty && !b.IsEmpty) { + if (a.Peek() != b.Peek()) + return false; + a = a.Pop(); + b = b.Pop(); + if (a == b) + return true; + } + return a.IsEmpty && b.IsEmpty; + } + + /// + /// Is called when the highlighting state at the end of the specified line has changed. + /// + /// This callback must not call HighlightLine or InvalidateHighlighting. + /// It may call GetSpanStack, but only for the changed line and lines above. + /// This method must not modify the document. + protected virtual void OnHighlightStateChanged(DocumentLine line, int lineNumber) + { + } + + #region Highlighting Engine + SpanStack spanStack; + + // local variables from HighlightLineInternal (are member because they are accessed by HighlighLine helper methods) + string lineText; + int lineStartOffset; + int position; + + /// + /// the HighlightedLine where highlighting output is being written to. + /// if this variable is null, nothing is highlighted and only the span state is updated + /// + HighlightedLine highlightedLine; + + void HighlightLineInternal(DocumentLine line) + { + lineStartOffset = line.Offset; + lineText = document.GetText(line.Offset, line.Length); + position = 0; + ResetColorStack(); + HighlightingRuleSet currentRuleSet = this.CurrentRuleSet; + Stack storedMatchArrays = new Stack(); + Match[] matches = AllocateMatchArray(currentRuleSet.Spans.Count); + Match endSpanMatch = null; + + while (true) { + for (int i = 0; i < matches.Length; i++) { + if (matches[i] == null || (matches[i].Success && matches[i].Index < position)) + matches[i] = currentRuleSet.Spans[i].StartExpression.Match(lineText, position); + } + if (endSpanMatch == null && !spanStack.IsEmpty) + endSpanMatch = spanStack.Peek().EndExpression.Match(lineText, position); + + Match firstMatch = Minimum(matches, endSpanMatch); + if (firstMatch == null) + break; + + HighlightNonSpans(firstMatch.Index); + + Debug.Assert(position == firstMatch.Index); + + if (firstMatch == endSpanMatch) { + HighlightingSpan poppedSpan = spanStack.Peek(); + if (!poppedSpan.SpanColorIncludesEnd) + PopColor(); // pop SpanColor + PushColor(poppedSpan.EndColor); + position = firstMatch.Index + firstMatch.Length; + PopColor(); // pop EndColor + if (poppedSpan.SpanColorIncludesEnd) + PopColor(); // pop SpanColor + spanStack = spanStack.Pop(); + currentRuleSet = this.CurrentRuleSet; + //FreeMatchArray(matches); + if (storedMatchArrays.Count > 0) { + matches = storedMatchArrays.Pop(); + int index = currentRuleSet.Spans.IndexOf(poppedSpan); + Debug.Assert(index >= 0 && index < matches.Length); + if (matches[index].Index == position) { + throw new InvalidOperationException( + "A highlighting span matched 0 characters, which would cause an endless loop.\n" + + "Change the highlighting definition so that either the start or the end regex matches at least one character.\n" + + "Start regex: " + poppedSpan.StartExpression + "\n" + + "End regex: " + poppedSpan.EndExpression); + } + } else { + matches = AllocateMatchArray(currentRuleSet.Spans.Count); + } + } else { + int index = Array.IndexOf(matches, firstMatch); + Debug.Assert(index >= 0); + HighlightingSpan newSpan = currentRuleSet.Spans[index]; + spanStack = spanStack.Push(newSpan); + currentRuleSet = this.CurrentRuleSet; + storedMatchArrays.Push(matches); + matches = AllocateMatchArray(currentRuleSet.Spans.Count); + if (newSpan.SpanColorIncludesStart) + PushColor(newSpan.SpanColor); + PushColor(newSpan.StartColor); + position = firstMatch.Index + firstMatch.Length; + PopColor(); + if (!newSpan.SpanColorIncludesStart) + PushColor(newSpan.SpanColor); + } + endSpanMatch = null; + } + HighlightNonSpans(line.Length); + + PopAllColors(); + } + + void HighlightNonSpans(int until) + { + Debug.Assert(position <= until); + if (position == until) + return; + if (highlightedLine != null) { + IList rules = CurrentRuleSet.Rules; + Match[] matches = AllocateMatchArray(rules.Count); + while (true) { + for (int i = 0; i < matches.Length; i++) { + if (matches[i] == null || (matches[i].Success && matches[i].Index < position)) + matches[i] = rules[i].Regex.Match(lineText, position, until - position); + } + Match firstMatch = Minimum(matches, null); + if (firstMatch == null) + break; + + position = firstMatch.Index; + int ruleIndex = Array.IndexOf(matches, firstMatch); + if (firstMatch.Length == 0) { + throw new InvalidOperationException( + "A highlighting rule matched 0 characters, which would cause an endless loop.\n" + + "Change the highlighting definition so that the rule matches at least one character.\n" + + "Regex: " + rules[ruleIndex].Regex); + } + PushColor(rules[ruleIndex].Color); + position = firstMatch.Index + firstMatch.Length; + PopColor(); + } + //FreeMatchArray(matches); + } + position = until; + } + + static readonly HighlightingRuleSet emptyRuleSet = new HighlightingRuleSet() { Name = "EmptyRuleSet" }; + + HighlightingRuleSet CurrentRuleSet { + get { + if (spanStack.IsEmpty) + return baseRuleSet; + else + return spanStack.Peek().RuleSet ?? emptyRuleSet; + } + } + #endregion + + #region Color Stack Management + Stack highlightedSectionStack; + HighlightedSection lastPoppedSection; + + void ResetColorStack() + { + Debug.Assert(position == 0); + lastPoppedSection = null; + if (highlightedLine == null) { + highlightedSectionStack = null; + } else { + highlightedSectionStack = new Stack(); + foreach (HighlightingSpan span in spanStack.Reverse()) { + PushColor(span.SpanColor); + } + } + } + + void PushColor(HighlightingColor color) + { + if (highlightedLine == null) + return; + if (color == null) { + highlightedSectionStack.Push(null); + } else if (lastPoppedSection != null && lastPoppedSection.Color == color + && lastPoppedSection.Offset + lastPoppedSection.Length == position + lineStartOffset) + { + highlightedSectionStack.Push(lastPoppedSection); + lastPoppedSection = null; + } else { + HighlightedSection hs = new HighlightedSection { + Offset = position + lineStartOffset, + Color = color + }; + highlightedLine.Sections.Add(hs); + highlightedSectionStack.Push(hs); + lastPoppedSection = null; + } + } + + void PopColor() + { + if (highlightedLine == null) + return; + HighlightedSection s = highlightedSectionStack.Pop(); + if (s != null) { + s.Length = (position + lineStartOffset) - s.Offset; + if (s.Length == 0) + highlightedLine.Sections.Remove(s); + else + lastPoppedSection = s; + } + } + + void PopAllColors() + { + if (highlightedSectionStack != null) { + while (highlightedSectionStack.Count > 0) + PopColor(); + } + } + #endregion + + #region Match helpers + /// + /// Returns the first match from the array or endSpanMatch. + /// + static Match Minimum(Match[] arr, Match endSpanMatch) + { + Match min = null; + foreach (Match v in arr) { + if (v.Success && (min == null || v.Index < min.Index)) + min = v; + } + if (endSpanMatch != null && endSpanMatch.Success && (min == null || endSpanMatch.Index < min.Index)) + return endSpanMatch; + else + return min; + } + + static Match[] AllocateMatchArray(int count) + { + if (count == 0) + return Empty.Array; + else + return new Match[count]; + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedInlineBuilder.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedInlineBuilder.cs new file mode 100644 index 000000000..ccfce3d49 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedInlineBuilder.cs @@ -0,0 +1,214 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Takes a series of highlighting commands and stores them. + /// Later, it can build inline objects (for use with WPF TextBlock) from the commands. + /// + /// + /// This class is not used in AvalonEdit - but it is useful for someone who wants to put a HighlightedLine + /// into a TextBlock. + /// In SharpDevelop, we use it to provide syntax highlighting inside the search results pad. + /// + public sealed class HighlightedInlineBuilder + { + sealed class HighlightingState + { + internal Brush Foreground; + internal Brush Background; + internal FontFamily Family; + internal FontWeight? Weight; + internal FontStyle? Style; + + public HighlightingState Clone() + { + return new HighlightingState { + Foreground = this.Foreground, + Background = this.Background, + Family = this.Family, + Weight = this.Weight, + Style = this.Style + }; + } + } + + readonly string text; + List stateChangeOffsets = new List(); + List stateChanges = new List(); + + int GetIndexForOffset(int offset) + { + if (offset < 0 || offset > text.Length) + throw new ArgumentOutOfRangeException("offset"); + int index = stateChangeOffsets.BinarySearch(offset); + if (index < 0) { + index = ~index; + if (offset < text.Length) { + stateChanges.Insert(index, stateChanges[index - 1].Clone()); + stateChangeOffsets.Insert(index, offset); + } + } + return index; + } + + /// + /// Creates a new HighlightedInlineBuilder instance. + /// + public HighlightedInlineBuilder(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + this.text = text; + stateChangeOffsets.Add(0); + stateChanges.Add(new HighlightingState()); + } + + HighlightedInlineBuilder(string text, int[] offsets, HighlightingState[] states) + { + this.text = text; + stateChangeOffsets.AddRange(offsets); + stateChanges.AddRange(states); + } + + /// + /// Gets the text. + /// + public string Text { + get { return text; } + } + + /// + /// Applies the properties from the HighlightingColor to the specified text segment. + /// + public void SetHighlighting(int offset, int length, HighlightingColor color) + { + if (color == null) + throw new ArgumentNullException("color"); + if (color.Foreground == null && color.FontStyle == null && color.FontWeight == null) { + // Optimization: don't split the HighlightingState when we're not changing + // any property. For example, the "Punctuation" color in C# is + // empty by default. + return; + } + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + HighlightingState state = stateChanges[i]; + if (color.Foreground != null) + state.Foreground = color.Foreground.GetBrush(null); + if (color.Background != null) + state.Background = color.Background.GetBrush(null); + if (color.FontStyle != null) + state.Style = color.FontStyle; + if (color.FontWeight != null) + state.Weight = color.FontWeight; + } + } + + /// + /// Sets the foreground brush on the specified text segment. + /// + public void SetForeground(int offset, int length, Brush brush) + { + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Foreground = brush; + } + } + + /// + /// Sets the background brush on the specified text segment. + /// + public void SetBackground(int offset, int length, Brush brush) + { + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Background = brush; + } + } + + /// + /// Sets the font weight on the specified text segment. + /// + public void SetFontWeight(int offset, int length, FontWeight weight) + { + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Weight = weight; + } + } + + /// + /// Sets the font style on the specified text segment. + /// + public void SetFontStyle(int offset, int length, FontStyle style) + { + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Style = style; + } + } + + /// + /// Sets the font family on the specified text segment. + /// + public void SetFontFamily(int offset, int length, FontFamily family) + { + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Family = family; + } + } + + /// + /// Creates WPF Run instances that can be used for TextBlock.Inlines. + /// + public Run[] CreateRuns() + { + Run[] runs = new Run[stateChanges.Count]; + for (int i = 0; i < runs.Length; i++) { + int startOffset = stateChangeOffsets[i]; + int endOffset = i + 1 < stateChangeOffsets.Count ? stateChangeOffsets[i + 1] : text.Length; + Run r = new Run(text.Substring(startOffset, endOffset - startOffset)); + HighlightingState state = stateChanges[i]; + if (state.Foreground != null) + r.Foreground = state.Foreground; + if (state.Background != null) + r.Background = state.Background; + if (state.Weight != null) + r.FontWeight = state.Weight.Value; + if (state.Family != null) + r.FontFamily = state.Family; + if (state.Style != null) + r.FontStyle = state.Style.Value; + runs[i] = r; + } + return runs; + } + + /// + /// Clones this HighlightedInlineBuilder. + /// + public HighlightedInlineBuilder Clone() + { + return new HighlightedInlineBuilder(this.text, + stateChangeOffsets.ToArray(), + stateChanges.Select(sc => sc.Clone()).ToArray()); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedLine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedLine.cs new file mode 100644 index 000000000..e6932c0aa --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedLine.cs @@ -0,0 +1,154 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Represents a highlighted document line. + /// + public class HighlightedLine + { + /// + /// Creates a new HighlightedLine instance. + /// + public HighlightedLine(TextDocument document, DocumentLine documentLine) + { + if (document == null) + throw new ArgumentNullException("document"); + if (!document.Lines.Contains(documentLine)) + throw new ArgumentException("Line is null or not part of document"); + this.Document = document; + this.DocumentLine = documentLine; + this.Sections = new NullSafeCollection(); + } + + /// + /// Gets the document associated with this HighlightedLine. + /// + public TextDocument Document { get; private set; } + + /// + /// Gets the document line associated with this HighlightedLine. + /// + public DocumentLine DocumentLine { get; private set; } + + /// + /// Gets the highlighted sections. + /// The sections are not overlapping, but they may be nested. + /// In that case, outer sections come in the list before inner sections. + /// The sections are sorted by start offset. + /// + public IList Sections { get; private set; } + + /// + /// Gets the default color of all text outside a . + /// + public HighlightingColor DefaultTextColor { get; set; } + + sealed class HtmlElement : IComparable + { + internal readonly int Offset; + internal readonly int Nesting; + internal readonly bool IsEnd; + internal readonly HighlightingColor Color; + + public HtmlElement(int offset, int nesting, bool isEnd, HighlightingColor color) + { + this.Offset = offset; + this.Nesting = nesting; + this.IsEnd = isEnd; + this.Color = color; + } + + public int CompareTo(HtmlElement other) + { + int r = Offset.CompareTo(other.Offset); + if (r != 0) + return r; + if (IsEnd != other.IsEnd) { + if (IsEnd) + return -1; + else + return 1; + } else { + if (IsEnd) + return other.Nesting.CompareTo(Nesting); + else + return Nesting.CompareTo(other.Nesting); + } + } + } + + /// + /// Produces HTML code for the line, with <span class="colorName"> tags. + /// + public string ToHtml(HtmlOptions options) + { + int startOffset = this.DocumentLine.Offset; + return ToHtml(startOffset, startOffset + this.DocumentLine.Length, options); + } + + /// + /// Produces HTML code for a section of the line, with <span class="colorName"> tags. + /// + public string ToHtml(int startOffset, int endOffset, HtmlOptions options) + { + if (options == null) + throw new ArgumentNullException("options"); + int documentLineStartOffset = this.DocumentLine.Offset; + int documentLineEndOffset = documentLineStartOffset + this.DocumentLine.Length; + if (startOffset < documentLineStartOffset || startOffset > documentLineEndOffset) + throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between " + documentLineStartOffset + " and " + documentLineEndOffset); + if (endOffset < startOffset || endOffset > documentLineEndOffset) + throw new ArgumentOutOfRangeException("endOffset", endOffset, "Value must be between startOffset and " + documentLineEndOffset); + ISegment requestedSegment = new SimpleSegment(startOffset, endOffset - startOffset); + + List elements = new List(); + for (int i = 0; i < this.Sections.Count; i++) { + HighlightedSection s = this.Sections[i]; + if (s.GetOverlap(requestedSegment).Length > 0) { + elements.Add(new HtmlElement(s.Offset, i, false, s.Color)); + elements.Add(new HtmlElement(s.Offset + s.Length, i, true, s.Color)); + } + } + elements.Sort(); + + TextDocument document = this.Document; + StringWriter w = new StringWriter(CultureInfo.InvariantCulture); + int textOffset = startOffset; + foreach (HtmlElement e in elements) { + int newOffset = Math.Min(e.Offset, endOffset); + if (newOffset > startOffset) { + HtmlClipboard.EscapeHtml(w, document.GetText(textOffset, newOffset - textOffset), options); + } + textOffset = Math.Max(textOffset, newOffset); + if (options.ColorNeedsSpanForStyling(e.Color)) { + if (e.IsEnd) { + w.Write(""); + } else { + w.Write("'); + } + } + } + HtmlClipboard.EscapeHtml(w, document.GetText(textOffset, endOffset - textOffset), options); + return w.ToString(); + } + + /// + public override string ToString() + { + return "[" + GetType().Name + " " + ToHtml(new HtmlOptions()) + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedSection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedSection.cs new file mode 100644 index 000000000..9e6fb56f1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightedSection.cs @@ -0,0 +1,33 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A text section with syntax highlighting information. + /// + public class HighlightedSection : ISegment + { + /// + /// Gets/sets the document offset of the section. + /// + public int Offset { get; set; } + + /// + /// Gets/sets the length of the section. + /// + public int Length { get; set; } + + int ISegment.EndOffset { + get { return this.Offset + this.Length; } + } + + /// + /// Gets the highlighting color associated with the highlighted section. + /// + public HighlightingColor Color { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingBrush.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingBrush.cs new file mode 100644 index 000000000..2e99013f3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingBrush.cs @@ -0,0 +1,117 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Windows; +using System.Windows.Media; + +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A brush used for syntax highlighting. Can retrieve a real brush on-demand. + /// + [Serializable] + public abstract class HighlightingBrush + { + /// + /// Gets the real brush. + /// + /// The construction context. context can be null! + public abstract Brush GetBrush(ITextRunConstructionContext context); + + /// + /// Gets the color of the brush. + /// + /// The construction context. context can be null! + public virtual Color? GetColor(ITextRunConstructionContext context) + { + SolidColorBrush scb = GetBrush(context) as SolidColorBrush; + if (scb != null) + return scb.Color; + else + return null; + } + } + + /// + /// Highlighting brush implementation that takes a frozen brush. + /// + [Serializable] + sealed class SimpleHighlightingBrush : HighlightingBrush, ISerializable + { + readonly SolidColorBrush brush; + + public SimpleHighlightingBrush(SolidColorBrush brush) + { + brush.Freeze(); + this.brush = brush; + } + + public SimpleHighlightingBrush(Color color) : this(new SolidColorBrush(color)) {} + + public override Brush GetBrush(ITextRunConstructionContext context) + { + return brush; + } + + public override string ToString() + { + return brush.ToString(); + } + + SimpleHighlightingBrush(SerializationInfo info, StreamingContext context) + { + this.brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(info.GetString("color"))); + brush.Freeze(); + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("color", brush.Color.ToString(CultureInfo.InvariantCulture)); + } + } + + /// + /// HighlightingBrush implementation that finds a brush using a resource. + /// + [Serializable] + sealed class SystemColorHighlightingBrush : HighlightingBrush, ISerializable + { + readonly PropertyInfo property; + + public SystemColorHighlightingBrush(PropertyInfo property) + { + Debug.Assert(property.ReflectedType == typeof(SystemColors)); + Debug.Assert(typeof(Brush).IsAssignableFrom(property.PropertyType)); + this.property = property; + } + + public override Brush GetBrush(ITextRunConstructionContext context) + { + return (Brush)property.GetValue(null, null); + } + + public override string ToString() + { + return property.Name; + } + + SystemColorHighlightingBrush(SerializationInfo info, StreamingContext context) + { + property = typeof(SystemColors).GetProperty(info.GetString("propertyName")); + if (property == null) + throw new ArgumentException("Error deserializing SystemColorHighlightingBrush"); + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("propertyName", property.Name); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColor.cs new file mode 100644 index 000000000..f1b1a25c8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColor.cs @@ -0,0 +1,119 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; +using System.Windows; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A highlighting color is a set of font properties and foreground and background color. + /// + [Serializable] + public class HighlightingColor : ISerializable + { + /// + /// Gets/Sets the name of the color. + /// + public string Name { get; set; } + + /// + /// Gets/sets the font weight. Null if the highlighting color does not change the font weight. + /// + public FontWeight? FontWeight { get; set; } + + /// + /// Gets/sets the font style. Null if the highlighting color does not change the font style. + /// + public FontStyle? FontStyle { get; set; } + + /// + /// Gets/sets the foreground color applied by the highlighting. + /// + public HighlightingBrush Foreground { get; set; } + + /// + /// Gets/sets the background color applied by the highlighting. + /// + public HighlightingBrush Background { get; set; } + + /// + /// Creates a new HighlightingColor instance. + /// + public HighlightingColor() + { + } + + /// + /// Deserializes a HighlightingColor. + /// + protected HighlightingColor(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException("info"); + this.Name = info.GetString("Name"); + if (info.GetBoolean("HasWeight")) + this.FontWeight = System.Windows.FontWeight.FromOpenTypeWeight(info.GetInt32("Weight")); + if (info.GetBoolean("HasStyle")) + this.FontStyle = (FontStyle?)new FontStyleConverter().ConvertFromInvariantString(info.GetString("Style")); + this.Foreground = (HighlightingBrush)info.GetValue("Foreground", typeof(HighlightingBrush)); + this.Background = (HighlightingBrush)info.GetValue("Background", typeof(HighlightingBrush)); + } + + /// + /// Serializes this HighlightingColor instance. + /// + [System.Security.SecurityCritical] + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException("info"); + info.AddValue("Name", this.Name); + info.AddValue("HasWeight", this.FontWeight.HasValue); + if (this.FontWeight.HasValue) + info.AddValue("Weight", this.FontWeight.Value.ToOpenTypeWeight()); + info.AddValue("HasStyle", this.FontStyle.HasValue); + if (this.FontStyle.HasValue) + info.AddValue("Style", this.FontStyle.Value.ToString()); + info.AddValue("Foreground", this.Foreground); + info.AddValue("Background", this.Background); + } + + /// + /// Gets CSS code for the color. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "CSS usually uses lowercase, and all possible values are English-only")] + public virtual string ToCss() + { + StringBuilder b = new StringBuilder(); + if (Foreground != null) { + Color? c = Foreground.GetColor(null); + if (c != null) { + b.AppendFormat(CultureInfo.InvariantCulture, "color: #{0:x2}{1:x2}{2:x2}; ", c.Value.R, c.Value.G, c.Value.B); + } + } + if (FontWeight != null) { + b.Append("font-weight: "); + b.Append(FontWeight.Value.ToString().ToLowerInvariant()); + b.Append("; "); + } + if (FontStyle != null) { + b.Append("font-style: "); + b.Append(FontStyle.Value.ToString().ToLowerInvariant()); + b.Append("; "); + } + return b.ToString(); + } + + /// + public override string ToString() + { + return "[" + GetType() + " " + (string.IsNullOrEmpty(this.Name) ? ToCss() : this.Name) + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColorizer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColorizer.cs new file mode 100644 index 000000000..9dfcc3976 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingColorizer.cs @@ -0,0 +1,286 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +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.Highlighting +{ + /// + /// A colorizes that interprets a highlighting rule set and colors the document accordingly. + /// + public class HighlightingColorizer : DocumentColorizingTransformer + { + readonly HighlightingRuleSet ruleSet; + + /// + /// Creates a new HighlightingColorizer instance. + /// + /// The root highlighting rule set. + public HighlightingColorizer(HighlightingRuleSet ruleSet) + { + if (ruleSet == null) + throw new ArgumentNullException("ruleSet"); + this.ruleSet = ruleSet; + } + + /// + /// This constructor is obsolete - please use the other overload instead. + /// + /// UNUSED + /// The root highlighting rule set. + [Obsolete("The TextView parameter is no longer used, please use the constructor taking only HighlightingRuleSet instead")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "textView")] + public HighlightingColorizer(TextView textView, HighlightingRuleSet ruleSet) + : this(ruleSet) + { + } + + void textView_DocumentChanged(object sender, EventArgs e) + { + OnDocumentChanged((TextView)sender); + } + + void OnDocumentChanged(TextView textView) + { + // remove existing highlighter, if any exists + textView.Services.RemoveService(typeof(IHighlighter)); + textView.Services.RemoveService(typeof(DocumentHighlighter)); + + TextDocument document = textView.Document; + if (document != null) { + IHighlighter highlighter = CreateHighlighter(textView, document); + textView.Services.AddService(typeof(IHighlighter), highlighter); + // for backward compatiblity, we're registering using both the interface and concrete types + if (highlighter is DocumentHighlighter) + textView.Services.AddService(typeof(DocumentHighlighter), highlighter); + } + } + + /// + /// Creates the IHighlighter instance for the specified text document. + /// + protected virtual IHighlighter CreateHighlighter(TextView textView, TextDocument document) + { + return new TextViewDocumentHighlighter(this, textView, document, ruleSet); + } + + /// + protected override void OnAddToTextView(TextView textView) + { + base.OnAddToTextView(textView); + textView.DocumentChanged += textView_DocumentChanged; + textView.VisualLineConstructionStarting += textView_VisualLineConstructionStarting; + OnDocumentChanged(textView); + } + + /// + protected override void OnRemoveFromTextView(TextView textView) + { + base.OnRemoveFromTextView(textView); + textView.Services.RemoveService(typeof(IHighlighter)); + textView.Services.RemoveService(typeof(DocumentHighlighter)); + textView.DocumentChanged -= textView_DocumentChanged; + textView.VisualLineConstructionStarting -= textView_VisualLineConstructionStarting; + } + + void textView_VisualLineConstructionStarting(object sender, VisualLineConstructionStartEventArgs e) + { + IHighlighter highlighter = ((TextView)sender).Services.GetService(typeof(IHighlighter)) as IHighlighter; + if (highlighter != null) { + // Force update of highlighting state up to the position where we start generating visual lines. + // This is necessary in case the document gets modified above the FirstLineInView so that the highlighting state changes. + // We need to detect this case and issue a redraw (through TextViewDocumentHighligher.OnHighlightStateChanged) + // before the visual line construction reuses existing lines that were built using the invalid highlighting state. + lineNumberBeingColorized = e.FirstLineInView.LineNumber - 1; + highlighter.GetSpanStack(lineNumberBeingColorized); + lineNumberBeingColorized = 0; + } + } + + DocumentLine lastColorizedLine; + + /// + protected override void Colorize(ITextRunConstructionContext context) + { + this.lastColorizedLine = null; + base.Colorize(context); + if (this.lastColorizedLine != context.VisualLine.LastDocumentLine) { + IHighlighter highlighter = context.TextView.Services.GetService(typeof(IHighlighter)) as IHighlighter; + if (highlighter != null) { + // In some cases, it is possible that we didn't highlight the last document line within the visual line + // (e.g. when the line ends with a fold marker). + // But even if we didn't highlight it, we'll have to update the highlighting state for it so that the + // proof inside TextViewDocumentHighlighter.OnHighlightStateChanged holds. + lineNumberBeingColorized = context.VisualLine.LastDocumentLine.LineNumber; + highlighter.GetSpanStack(lineNumberBeingColorized); + lineNumberBeingColorized = 0; + } + } + this.lastColorizedLine = null; + } + + int lineNumberBeingColorized; + + /// + protected override void ColorizeLine(DocumentLine line) + { + IHighlighter highlighter = CurrentContext.TextView.Services.GetService(typeof(IHighlighter)) as IHighlighter; + if (highlighter != null) { + lineNumberBeingColorized = line.LineNumber; + HighlightedLine hl = highlighter.HighlightLine(lineNumberBeingColorized); + lineNumberBeingColorized = 0; + foreach (HighlightedSection section in hl.Sections) { + if (IsEmptyColor(section.Color)) + continue; + ChangeLinePart(section.Offset, section.Offset + section.Length, + visualLineElement => ApplyColorToElement(visualLineElement, section.Color)); + } + } + this.lastColorizedLine = line; + } + + /// + /// Gets whether the color is empty (has no effect on a VisualLineTextElement). + /// For example, the C# "Punctuation" is an empty color. + /// + bool IsEmptyColor(HighlightingColor color) + { + if (color == null) + return true; + return color.Background == null && color.Foreground == null + && color.FontStyle == null && color.FontWeight == null; + } + + /// + /// Applies a highlighting color to a visual line element. + /// + protected virtual void ApplyColorToElement(VisualLineElement element, HighlightingColor color) + { + if (color.Foreground != null) { + Brush b = color.Foreground.GetBrush(CurrentContext); + if (b != null) + element.TextRunProperties.SetForegroundBrush(b); + } + if (color.Background != null) { + Brush b = color.Background.GetBrush(CurrentContext); + if (b != null) + element.BackgroundBrush = b; + } + if (color.FontStyle != null || color.FontWeight != null) { + Typeface tf = element.TextRunProperties.Typeface; + element.TextRunProperties.SetTypeface(new Typeface( + tf.FontFamily, + color.FontStyle ?? tf.Style, + color.FontWeight ?? tf.Weight, + tf.Stretch + )); + } + } + + /// + /// This class is responsible for telling the TextView to redraw lines when the highlighting state has changed. + /// + /// + /// Creation of a VisualLine triggers the syntax highlighter (which works on-demand), so it says: + /// Hey, the user typed "/*". Don't just recreate that line, but also the next one + /// because my highlighting state (at end of line) changed! + /// + sealed class TextViewDocumentHighlighter : DocumentHighlighter + { + readonly HighlightingColorizer colorizer; + readonly TextView textView; + + public TextViewDocumentHighlighter(HighlightingColorizer colorizer, TextView textView, TextDocument document, HighlightingRuleSet baseRuleSet) + : base(document, baseRuleSet) + { + Debug.Assert(colorizer != null); + Debug.Assert(textView != null); + this.colorizer = colorizer; + this.textView = textView; + } + + protected override void OnHighlightStateChanged(DocumentLine line, int lineNumber) + { + base.OnHighlightStateChanged(line, lineNumber); + if (colorizer.lineNumberBeingColorized != lineNumber) { + // Ignore notifications for any line except the one we're interested in. + // This improves the performance as Redraw() can take quite some time when called repeatedly + // while scanning the document (above the visible area) for highlighting changes. + return; + } + if (textView.Document != this.Document) { + // May happen if document on text view was changed but some user code is still using the + // existing IHighlighter instance. + return; + } + + // The user may have inserted "/*" into the current line, and so far only that line got redrawn. + // So when the highlighting state is changed, we issue a redraw for the line immediately below. + // If the highlighting state change applies to the lines below, too, the construction of each line + // will invalidate the next line, and the construction pass will regenerate all lines. + + //Debug.WriteLine("OnHighlightStateChanged forces redraw of line " + (lineNumber + 1)); + + // If the VisualLine construction is in progress, we have to avoid sending redraw commands for + // anything above the line currently being constructed. + // It takes some explanation to see why this cannot happen. + // VisualLines always get constructed from top to bottom. + // Each VisualLine construction calls into the highlighter and thus forces an update of the + // highlighting state for all lines up to the one being constructed. + + // To guarantee that we don't redraw lines we just constructed, we need to show that when + // a VisualLine is being reused, the highlighting state at that location is still up-to-date. + + // This isn't exactly trivial and the initial implementation was incorrect in the presence of external document changes + // (e.g. split view). + + // For the first line in the view, the TextView.VisualLineConstructionStarting event is used to check that the + // highlighting state is up-to-date. If it isn't, this method will be executed, and it'll mark the first line + // in the view as requiring a redraw. This is safely possible because that event occurs before any lines are reused. + + // Once we take care of the first visual line, we won't get in trouble with other lines due to the top-to-bottom + // construction process. + + // We'll prove that: if line N is being reused, then the highlighting state is up-to-date until (end of) line N-1. + + // Start of induction: the first line in view is reused only if the highlighting state was up-to-date + // until line N-1 (no change detected in VisualLineConstructionStarting event). + + // Induction step: + // If another line N+1 is being reused, then either + // a) the previous line (the visual line containing document line N) was newly constructed + // or b) the previous line was reused + // In case a, the construction updated the highlighting state. This means the stack at end of line N is up-to-date. + // In case b, the highlighting state at N-1 was up-to-date, and the text of line N was not changed. + // (if the text was changed, the line could not have been reused). + // From this follows that the highlighting state at N is still up-to-date. + + // The above proof holds even in the presence of folding: folding only ever hides text in the middle of a visual line. + // Our Colorize-override ensures that the highlighting state is always updated for the LastDocumentLine, + // so it will always invalidate the next visual line when a folded line is constructed + // and the highlighting stack has changed. + + textView.Redraw(line.NextLine, DispatcherPriority.Normal); + + /* + * Meta-comment: "why does this have to be so complicated?" + * + * The problem is that I want to re-highlight only on-demand and incrementally; + * and at the same time only repaint changed lines. + * So the highlighter and the VisualLine construction both have to run in a single pass. + * The highlighter must take care that it never touches already constructed visual lines; + * if it detects that something must be redrawn because the highlighting state changed, + * it must do so early enough in the construction process. + * But doing it too early means it doesn't have the information necessary to re-highlight and redraw only the desired parts. + */ + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionInvalidException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionInvalidException.cs new file mode 100644 index 000000000..dccd6a481 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionInvalidException.cs @@ -0,0 +1,43 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Runtime.Serialization; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Indicates that the highlighting definition that was tried to load was invalid. + /// + [Serializable()] + public class HighlightingDefinitionInvalidException : Exception + { + /// + /// Creates a new HighlightingDefinitionInvalidException instance. + /// + public HighlightingDefinitionInvalidException() : base() + { + } + + /// + /// Creates a new HighlightingDefinitionInvalidException instance. + /// + public HighlightingDefinitionInvalidException(string message) : base(message) + { + } + + /// + /// Creates a new HighlightingDefinitionInvalidException instance. + /// + public HighlightingDefinitionInvalidException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Creates a new HighlightingDefinitionInvalidException instance. + /// + protected HighlightingDefinitionInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionTypeConverter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionTypeConverter.cs new file mode 100644 index 000000000..7463d9353 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingDefinitionTypeConverter.cs @@ -0,0 +1,54 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Converts between strings and by treating the string as the definition name + /// and calling HighlightingManager.Instance.GetDefinition(name). + /// + public sealed class HighlightingDefinitionTypeConverter : TypeConverter + { + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + else + return base.CanConvertFrom(context, sourceType); + } + + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + string definitionName = value as string; + if (definitionName != null) + return HighlightingManager.Instance.GetDefinition(definitionName); + else + return base.ConvertFrom(context, culture, value); + } + + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + else + return base.CanConvertTo(context, destinationType); + } + + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + IHighlightingDefinition definition = value as IHighlightingDefinition; + if (definition != null && destinationType == typeof(string)) + return definition.Name; + else + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingManager.cs new file mode 100644 index 000000000..8db4787d7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingManager.cs @@ -0,0 +1,290 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Xml; + +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Manages a list of syntax highlighting definitions. + /// + /// + /// All memers on this class (including instance members) are thread-safe. + /// + public class HighlightingManager : IHighlightingDefinitionReferenceResolver + { + sealed class DelayLoadedHighlightingDefinition : IHighlightingDefinition2 + { + readonly object lockObj = new object(); + readonly string name; + Func lazyLoadingFunction; + IHighlightingDefinition definition; + Exception storedException; + + public DelayLoadedHighlightingDefinition(string name, Func lazyLoadingFunction) + { + this.name = name; + this.lazyLoadingFunction = lazyLoadingFunction; + } + + public string Name { + get { + if (name != null) + return name; + else + return GetDefinition().Name; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "The exception will be rethrown")] + IHighlightingDefinition GetDefinition() + { + Func func; + lock (lockObj) { + if (this.definition != null) + return this.definition; + func = this.lazyLoadingFunction; + } + Exception exception = null; + IHighlightingDefinition def = null; + try { + using (var busyLock = BusyManager.Enter(this)) { + if (!busyLock.Success) + throw new InvalidOperationException("Tried to create delay-loaded highlighting definition recursively. Make sure the are no cyclic references between the highlighting definitions."); + def = func(); + } + if (def == null) + throw new InvalidOperationException("Function for delay-loading highlighting definition returned null"); + } catch (Exception ex) { + exception = ex; + } + lock (lockObj) { + this.lazyLoadingFunction = null; + if (this.definition == null && this.storedException == null) { + this.definition = def; + this.storedException = exception; + } + if (this.storedException != null) + throw new HighlightingDefinitionInvalidException("Error delay-loading highlighting definition", this.storedException); + return this.definition; + } + } + + public HighlightingRuleSet MainRuleSet { + get { + return GetDefinition().MainRuleSet; + } + } + + public HighlightingRuleSet GetNamedRuleSet(string name) + { + return GetDefinition().GetNamedRuleSet(name); + } + + public HighlightingColor GetNamedColor(string name) + { + return GetDefinition().GetNamedColor(name); + } + + public IEnumerable NamedHighlightingColors { + get { + return GetDefinition().NamedHighlightingColors; + } + } + + public override string ToString() + { + return this.Name; + } + + public IDictionary Properties { + get { + var def = GetDefinition() as IHighlightingDefinition2; + if (def != null) + return def.Properties; + return null; + } + } + } + + readonly object lockObj = new object(); + Dictionary highlightingsByName = new Dictionary(); + Dictionary highlightingsByExtension = new Dictionary(StringComparer.OrdinalIgnoreCase); + List allHighlightings = new List(); + + /// + /// Gets a highlighting definition by name. + /// Returns null if the definition is not found. + /// + public IHighlightingDefinition GetDefinition(string name) + { + lock (lockObj) { + IHighlightingDefinition rh; + if (highlightingsByName.TryGetValue(name, out rh)) + return rh; + else + return null; + } + } + + /// + /// Gets a copy of all highlightings. + /// + public ReadOnlyCollection HighlightingDefinitions { + get { + lock (lockObj) { + return Array.AsReadOnly(allHighlightings.ToArray()); + } + } + } + + /// + /// Gets the names of the registered highlightings. + /// + [ObsoleteAttribute("Use the HighlightingDefinitions property instead.")] + public IEnumerable HighlightingNames { + get { + lock (lockObj) { + return new List(highlightingsByName.Keys); + } + } + } + + /// + /// Gets a highlighting definition by extension. + /// Returns null if the definition is not found. + /// + public IHighlightingDefinition GetDefinitionByExtension(string extension) + { + lock (lockObj) { + IHighlightingDefinition rh; + if (highlightingsByExtension.TryGetValue(extension, out rh)) + return rh; + else + return null; + } + } + + /// + /// Registers a highlighting definition. + /// + /// The name to register the definition with. + /// The file extensions to register the definition for. + /// The highlighting definition. + public void RegisterHighlighting(string name, string[] extensions, IHighlightingDefinition highlighting) + { + if (highlighting == null) + throw new ArgumentNullException("highlighting"); + + lock (lockObj) { + allHighlightings.Add(highlighting); + if (name != null) { + highlightingsByName[name] = highlighting; + } + if (extensions != null) { + foreach (string ext in extensions) { + highlightingsByExtension[ext] = highlighting; + } + } + } + } + + /// + /// Registers a highlighting definition. + /// + /// The name to register the definition with. + /// The file extensions to register the definition for. + /// A function that loads the highlighting definition. + public void RegisterHighlighting(string name, string[] extensions, Func lazyLoadedHighlighting) + { + if (lazyLoadedHighlighting == null) + throw new ArgumentNullException("lazyLoadedHighlighting"); + RegisterHighlighting(name, extensions, new DelayLoadedHighlightingDefinition(name, lazyLoadedHighlighting)); + } + + /// + /// Gets the default HighlightingManager instance. + /// The default HighlightingManager comes with built-in highlightings. + /// + public static HighlightingManager Instance { + get { + return DefaultHighlightingManager.Instance; + } + } + + internal sealed class DefaultHighlightingManager : HighlightingManager + { + public new static readonly DefaultHighlightingManager Instance = new DefaultHighlightingManager(); + + public DefaultHighlightingManager() + { + Resources.RegisterBuiltInHighlightings(this); + } + + // Registering a built-in highlighting + internal void RegisterHighlighting(string name, string[] extensions, string resourceName) + { + try { + #if DEBUG + // don't use lazy-loading in debug builds, show errors immediately + Xshd.XshdSyntaxDefinition xshd; + using (Stream s = Resources.OpenStream(resourceName)) { + using (XmlTextReader reader = new XmlTextReader(s)) { + xshd = Xshd.HighlightingLoader.LoadXshd(reader, false); + } + } + Debug.Assert(name == xshd.Name); + if (extensions != null) + Debug.Assert(System.Linq.Enumerable.SequenceEqual(extensions, xshd.Extensions)); + else + Debug.Assert(xshd.Extensions.Count == 0); + + // round-trip xshd: +// string resourceFileName = Path.Combine(Path.GetTempPath(), resourceName); +// using (XmlTextWriter writer = new XmlTextWriter(resourceFileName, System.Text.Encoding.UTF8)) { +// writer.Formatting = Formatting.Indented; +// new Xshd.SaveXshdVisitor(writer).WriteDefinition(xshd); +// } +// using (FileStream fs = File.Create(resourceFileName + ".bin")) { +// new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Serialize(fs, xshd); +// } +// using (FileStream fs = File.Create(resourceFileName + ".compiled")) { +// new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Serialize(fs, Xshd.HighlightingLoader.Load(xshd, this)); +// } + + RegisterHighlighting(name, extensions, Xshd.HighlightingLoader.Load(xshd, this)); + #else + RegisterHighlighting(name, extensions, LoadHighlighting(resourceName)); + #endif + } catch (HighlightingDefinitionInvalidException ex) { + throw new InvalidOperationException("The built-in highlighting '" + name + "' is invalid.", ex); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", + Justification = "LoadHighlighting is used only in release builds")] + Func LoadHighlighting(string resourceName) + { + Func func = delegate { + Xshd.XshdSyntaxDefinition xshd; + using (Stream s = Resources.OpenStream(resourceName)) { + using (XmlTextReader reader = new XmlTextReader(s)) { + // in release builds, skip validating the built-in highlightings + xshd = Xshd.HighlightingLoader.LoadXshd(reader, true); + } + } + return Xshd.HighlightingLoader.Load(xshd, this); + }; + return func; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRule.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRule.cs new file mode 100644 index 000000000..18edf4974 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRule.cs @@ -0,0 +1,31 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text.RegularExpressions; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A highlighting rule. + /// + [Serializable] + public class HighlightingRule + { + /// + /// Gets/Sets the regular expression for the rule. + /// + public Regex Regex { get; set; } + + /// + /// Gets/Sets the highlighting color. + /// + public HighlightingColor Color { get; set; } + + /// + public override string ToString() + { + return "[" + GetType().Name + " " + Regex + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRuleSet.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRuleSet.cs new file mode 100644 index 000000000..f887b9d54 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingRuleSet.cs @@ -0,0 +1,46 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A highlighting rule set describes a set of spans that are valid at a given code location. + /// + [Serializable] + public class HighlightingRuleSet + { + /// + /// Creates a new RuleSet instance. + /// + public HighlightingRuleSet() + { + this.Spans = new NullSafeCollection(); + this.Rules = new NullSafeCollection(); + } + + /// + /// Gets/Sets the name of the rule set. + /// + public string Name { get; set; } + + /// + /// Gets the list of spans. + /// + public IList Spans { get; private set; } + + /// + /// Gets the list of rules. + /// + public IList Rules { get; private set; } + + /// + public override string ToString() + { + return "[" + GetType().Name + " " + Name + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingSpan.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingSpan.cs new file mode 100644 index 000000000..b57e70a93 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HighlightingSpan.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text.RegularExpressions; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A highlighting span is a region with start+end expression that has a different RuleSet inside + /// and colors the region. + /// + [Serializable] + public class HighlightingSpan + { + /// + /// Gets/Sets the start expression. + /// + public Regex StartExpression { get; set; } + + /// + /// Gets/Sets the end expression. + /// + public Regex EndExpression { get; set; } + + /// + /// Gets/Sets the rule set that applies inside this span. + /// + public HighlightingRuleSet RuleSet { get; set; } + + /// + /// Gets the color used for the text matching the start expression. + /// + public HighlightingColor StartColor { get; set; } + + /// + /// Gets the color used for the text between start and end. + /// + public HighlightingColor SpanColor { get; set; } + + /// + /// Gets the color used for the text matching the end expression. + /// + public HighlightingColor EndColor { get; set; } + + /// + /// Gets/Sets whether the span color includes the start. + /// The default is false. + /// + public bool SpanColorIncludesStart { get; set; } + + /// + /// Gets/Sets whether the span color includes the end. + /// The default is false. + /// + public bool SpanColorIncludesEnd { get; set; } + + /// + public override string ToString() + { + return "[" + GetType().Name + " Start=" + StartExpression + ", End=" + EndExpression + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HtmlClipboard.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HtmlClipboard.cs new file mode 100644 index 000000000..a656308b7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/HtmlClipboard.cs @@ -0,0 +1,201 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Windows; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Allows copying HTML text to the clipboard. + /// + public static class HtmlClipboard + { + /// + /// Builds a header for the CF_HTML clipboard format. + /// + static string BuildHeader(int startHTML, int endHTML, int startFragment, int endFragment) + { + StringBuilder b = new StringBuilder(); + b.AppendLine("Version:0.9"); + b.AppendLine("StartHTML:" + startHTML.ToString("d8", CultureInfo.InvariantCulture)); + b.AppendLine("EndHTML:" + endHTML.ToString("d8", CultureInfo.InvariantCulture)); + b.AppendLine("StartFragment:" + startFragment.ToString("d8", CultureInfo.InvariantCulture)); + b.AppendLine("EndFragment:" + endFragment.ToString("d8", CultureInfo.InvariantCulture)); + return b.ToString(); + } + + /// + /// Sets the TextDataFormat.Html on the data object to the specified html fragment. + /// This helper methods takes care of creating the necessary CF_HTML header. + /// + public static void SetHtml(DataObject dataObject, string htmlFragment) + { + if (dataObject == null) + throw new ArgumentNullException("dataObject"); + if (htmlFragment == null) + throw new ArgumentNullException("htmlFragment"); + + string htmlStart = @"" + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine; + string htmlEnd = "" + Environment.NewLine + "" + Environment.NewLine + "" + Environment.NewLine; + string dummyHeader = BuildHeader(0, 0, 0, 0); + // the offsets are stored as UTF-8 bytes (see CF_HTML documentation) + int startHTML = dummyHeader.Length; + int startFragment = startHTML + htmlStart.Length; + int endFragment = startFragment + Encoding.UTF8.GetByteCount(htmlFragment); + int endHTML = endFragment + htmlEnd.Length; + string cf_html = BuildHeader(startHTML, endHTML, startFragment, endFragment) + htmlStart + htmlFragment + htmlEnd; + Debug.WriteLine(cf_html); + dataObject.SetText(cf_html, TextDataFormat.Html); + } + + /// + /// Creates a HTML fragment from a part of a document. + /// + /// The document to create HTML from. + /// The highlighter used to highlight the document. null is valid and will create HTML without any highlighting. + /// The part of the document to create HTML for. You can pass null to create HTML for the whole document. + /// The options for the HTML creation. + /// HTML code for the document part. + public static string CreateHtmlFragment(TextDocument document, IHighlighter highlighter, ISegment segment, HtmlOptions options) + { + if (document == null) + throw new ArgumentNullException("document"); + if (options == null) + throw new ArgumentNullException("options"); + if (highlighter != null && highlighter.Document != document) + throw new ArgumentException("Highlighter does not belong to the specified document."); + if (segment == null) + segment = new SimpleSegment(0, document.TextLength); + + StringBuilder html = new StringBuilder(); + int segmentEndOffset = segment.EndOffset; + DocumentLine line = document.GetLineByOffset(segment.Offset); + while (line != null && line.Offset < segmentEndOffset) { + HighlightedLine highlightedLine; + if (highlighter != null) + highlightedLine = highlighter.HighlightLine(line.LineNumber); + else + highlightedLine = new HighlightedLine(document, line); + SimpleSegment s = segment.GetOverlap(line); + if (html.Length > 0) + html.AppendLine("
"); + html.Append(highlightedLine.ToHtml(s.Offset, s.EndOffset, options)); + line = line.NextLine; + } + return html.ToString(); + } + + /// + /// Escapes text and writes the result to the StringBuilder. + /// + internal static void EscapeHtml(StringWriter w, string text, HtmlOptions options) + { + int spaceCount = -1; + foreach (char c in text) { + if (c == ' ') { + if (spaceCount < 0) + w.Write(" "); + else + spaceCount++; + } else if (c == '\t') { + if (spaceCount < 0) + spaceCount = 0; + spaceCount += options.TabSize; + } else { + if (spaceCount == 1) { + w.Write(' '); + } else if (spaceCount >= 1) { + for (int i = 0; i < spaceCount; i++) { + w.Write(" "); + } + } + spaceCount = 0; + switch (c) { + case '<': + w.Write("<"); + break; + case '>': + w.Write(">"); + break; + case '&': + w.Write("&"); + break; + case '"': + w.Write("""); + break; + default: + w.Write(c); + break; + } + } + } + for (int i = 0; i < spaceCount; i++) { + w.Write(" "); + } + } + } + + /// + /// Holds options for converting text to HTML. + /// + public class HtmlOptions + { + /// + /// Creates a default HtmlOptions instance. + /// + public HtmlOptions() + { + this.TabSize = 4; + } + + /// + /// Creates a new HtmlOptions instance that copies applicable options from the . + /// + public HtmlOptions(TextEditorOptions options) + : this() + { + if (options == null) + throw new ArgumentNullException("options"); + this.TabSize = options.IndentationSize; + } + + /// + /// The amount of spaces a tab gets converted to. + /// + public int TabSize { get; set; } + + /// + /// Writes the HTML attribute for the style to the text writer. + /// + public virtual void WriteStyleAttributeForColor(TextWriter writer, HighlightingColor color) + { + if (writer == null) + throw new ArgumentNullException("writer"); + if (color == null) + throw new ArgumentNullException("color"); + writer.Write(" style=\""); + writer.Write(color.ToCss()); + writer.Write("\""); + } + + /// + /// Gets whether the color needs to be written out to HTML. + /// + public virtual bool ColorNeedsSpanForStyling(HighlightingColor color) + { + if (color == null) + throw new ArgumentNullException("color"); + return !string.IsNullOrEmpty(color.ToCss()); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlighter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlighter.cs new file mode 100644 index 000000000..1b5cab138 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlighter.cs @@ -0,0 +1,35 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Represents a highlighted document. + /// + /// This interface is used by the to register the highlighter as a TextView service. + public interface IHighlighter + { + /// + /// Gets the underlying text document. + /// + TextDocument Document { get; } + + /// + /// Gets the span stack at the end of the specified line. + /// -> GetSpanStack(1) returns the spans at the start of the second line. + /// + /// GetSpanStack(0) is valid and will always return the empty stack. + ImmutableStack GetSpanStack(int lineNumber); + + /// + /// Highlights the specified document line. + /// + /// The line to highlight. + /// A line object that represents the highlighted sections. + HighlightedLine HighlightLine(int lineNumber); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinition.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinition.cs new file mode 100644 index 000000000..0b42cd426 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinition.cs @@ -0,0 +1,54 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// A highlighting definition. + /// + [TypeConverter(typeof(HighlightingDefinitionTypeConverter))] + public interface IHighlightingDefinition + { + /// + /// Gets the name of the highlighting definition. + /// + string Name { get; } + + /// + /// Gets the main rule set. + /// + HighlightingRuleSet MainRuleSet { get; } + + /// + /// Gets a rule set by name. + /// + /// The rule set, or null if it is not found. + HighlightingRuleSet GetNamedRuleSet(string name); + + /// + /// Gets a named highlighting color. + /// + /// The highlighting color, or null if it is not found. + HighlightingColor GetNamedColor(string name); + + /// + /// Gets the list of named highlighting colors. + /// + IEnumerable NamedHighlightingColors { get; } + } + + /// + /// Extension of IHighlightingDefinition to avoid breaking changes in the API. + /// + public interface IHighlightingDefinition2 : IHighlightingDefinition + { + /// + /// Gets the list of properties. + /// + IDictionary Properties { get; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinitionReferenceResolver.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinitionReferenceResolver.cs new file mode 100644 index 000000000..d378ae1b7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/IHighlightingDefinitionReferenceResolver.cs @@ -0,0 +1,18 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting +{ + /// + /// Interface for resolvers that can solve cross-definition references. + /// + public interface IHighlightingDefinitionReferenceResolver + { + /// + /// Gets the highlighting definition by name, or null if it is not found. + /// + IHighlightingDefinition GetDefinition(string name); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs new file mode 100644 index 000000000..a05d1fc75 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Highlighting +{ + public class OffsetColorizer : DocumentColorizingTransformer + { + public int StartOffset { get; set; } + public int EndOffset { get; set; } + public Brush Brush { get; set; } + public DocumentLine Line { get; set; } + + public OffsetColorizer(DocumentLine line, int start, int end, Brush brush) + { + Line = line; + StartOffset = start; + EndOffset = end; + Brush = brush; + } + + protected override void ColorizeLine(DocumentLine line) + { + if (Line == line) + { + try + { + ChangeLinePart(StartOffset, EndOffset, element => element.TextRunProperties.SetForegroundBrush(Brush)); + } + catch { } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ASPX.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ASPX.xshd new file mode 100644 index 000000000..bd0c922ac --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ASPX.xshd @@ -0,0 +1,16 @@ + + + + + + + + <% + %> + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Boo.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Boo.xshd new file mode 100644 index 000000000..a4e555198 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Boo.xshd @@ -0,0 +1,212 @@ + + + + + + + """ + """ + + + \# + + + // + + + /\* + \*/ + + + " + " + + + + + \$\{ + } + + + + + ' + ' + + + + + + @/ + / + + + + /\S+/ + + + + self + super + + + is + isa + and + or + not + + + else + elif + if + match + case + unless + otherwise + for + in + while + + + break + continue + return + yield + goto + + + try + raise + ensure + except + retry + success + + + fixed + unsafe + + + bool + double + single + byte + sbyte + short + ushort + int + uint + long + ulong + date + timespan + decimal + char + object + duck + string + regex + + + void + + + cast + as + + + override + static + virtual + abstract + final + transient + partial + + + public + protected + private + internal + + + namespace + import + from + + + get + set + + + null + value + true + false + ast + + + using + unchecked + checked + lock + getter + required + rawArrayIndexing + normalArrayIndexing + yieldAll + + + assert + array + matrix + print + gets + prompt + enumerate + zip + filter + map + cat + __eval__ + __switch__ + + + constructor + destructor + def + include + event + ref + + + pass + + + enum + class + struct + interface + mixin + callable + do + of + + [\d\w_]+(?=(\s*\()) + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + + /\* + \*/ + + + /\* + \*/ + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CPP-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CPP-Mode.xshd new file mode 100644 index 000000000..f552ad413 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CPP-Mode.xshd @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + [?,.;()\[\]{}+\-/%*<>^=~!&]+ + + + __abstract + __box + __delegate + __gc + __identifier + __nogc + __pin + __property + __sealed + __try_cast + __typeof + __value + __event + __hook + __raise + __unhook + __interface + ref class + ref struct + value class + value struct + interface class + interface struct + enum class + enum struct + delegate + event + property + abstract + override + sealed + generic + where + finally + for each + gcnew + in + initonly + literal + nullptr + + + this + + + and + and_eq + bitand + bitor + new + not + not_eq + or + or_eq + xor + xor_eq + + + using + namespace + + + friend + + + private + protected + public + const + volatile + static + + + bool + char + unsigned + union + virtual + double + float + short + signed + void + class + enum + struct + + + false + true + + + do + for + while + + + break + continue + goto + return + + + catch + throw + try + + + case + else + if + switch + default + + + asm + auto + compl + mutable + const_cast + delete + dynamic_cast + explicit + export + extern + inline + int + long + operator + register + reinterpret_cast + sizeof + static_cast + template + typedef + typeid + typename + + + \# + + + // + + + /\* + \*/ + + + " + " + + + + + + ' + ' + + + + + [\d\w_]+(?=(\s*\()) + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSS-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSS-Mode.xshd new file mode 100644 index 000000000..3ef562f0d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSS-Mode.xshd @@ -0,0 +1,57 @@ + + + + + + + + + + + + + /\* + \*/ + + + \{ + \} + + + \# + \s + + [\d\w] + + + + + /\* + \*/ + + + \: + \;|(?=\}) + + + " + " + + + + + + ' + ' + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd new file mode 100644 index 000000000..40f362e08 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TODO + FIXME + + + HACK + UNDONE + + + + + + + \# + + + + (define|undef|if|elif|else|endif|line)\b + + + + // + + + + + + (region|endregion|error|warning|pragma)\b + + + + + + + + + // + + + + /\* + \*/ + + + + " + " + + + + + + + + ' + ' + + + + + + + + @" + " + + + + + + + + + @[\w\d_]+ + + + + include + import + + + + this + base + + + + as + is + new + sizeof + typeof + stackalloc + + + + true + false + + + + else + if + switch + case + default + do + for + foreach + in + while + lock + + + + break + continue + goto + return + + + + yield + partial + class + global + where + select + group + by + into + from + ascending + descending + orderby + let + join + on + equals + var + dynamic + await + void + interface + this + + + + try + throw + catch + finally + + + + checked + unchecked + + + + fixed + unsafe + + + + bool + byte + char + decimal + double + enum + float + int + string + long + sbyte + short + struct + uint + ushort + ulong + null + + + + @ReferenceTypes@ + + + + @InterfaceTypes@ + + + + explicit + implicit + operator + + + + params + ref + out + + + + abstract + const + event + extern + override + readonly + sealed + static + virtual + volatile + async + + + + public + protected + private + internal + + + + namespace + using + + + + get + set + add + remove + + + + null + value + + + + + \b + [\d\w_]+ # an identifier + (?=\s*\() # followed by ( + + + + + \b0[xX][0-9a-fA-F]+ # hex number + | + ( \b\d+(\.[0-9]+)? #number with optional floating point + | \.[0-9]+ #or just starting with floating point + ) + ([eE][+-]?[0-9]+)? # optional exponent + + + + [?,.;()\[\]{}+\-/%*<>^+~!|&]+ + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Coco-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Coco-Mode.xshd new file mode 100644 index 000000000..9395198b5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Coco-Mode.xshd @@ -0,0 +1,74 @@ + + + + + + + + + + + [{}\(\)\[\]|+\-=\.]+ + + + ANY + CHARACTERS + COMMENTS + COMPILER + CONTEXT + END + FROM + IF + IGNORE + NAMESPACE + NESTED + PRAGMAS + PRODUCTIONS + SYNC + TO + TOKENS + TOKENNAMES + WEAK + using + + + // + + + /\* + \*/ + + + COMPILER + TOKENNAMES + + + " + " + + + ' + ' + + + < + > + + + \(\. + \.\) + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/HTML-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/HTML-Mode.xshd new file mode 100644 index 000000000..fd8211fe4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/HTML-Mode.xshd @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + <!-- + --> + + + <script\ lang="JavaScript"> + </script> + + + <script\s.*?text/javascript.*?> + </script> + + + <script\ lang="JScript"> + </script> + + + <script\ lang="VBScript"> + </script> + + + <script> + </script> + + + <script[^\w\d_] + </script> + + + < + > + + + & + ; + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + aacute + agrave + acirc + amp + atilde + aring + auml + aelig + ccedil + copy + eacute + egrave + ecirc + euml + iacute + igrave + icirc + iuml + eth + gt + lt + nbsp + ntilde + oacute + ograve + ocirc + otilde + ouml + oslash + quot + reg + szlig + uacute + ugrave + ucirc + uuml + yacute + thorn + trade + yuml + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + / + + + = + + + !DOCTYPE + A + ABBR + ACRONYM + ADDRESS + APPLET + AREA + B + BASE + BASEFONT + BGSOUND + BDO + BIG + BLINK + BLOCKQUOTE + BODY + BR + BUTTON + CAPTION + CENTER + CITE + CODE + COL + COLGROUP + COMMENT + DD + DEL + DFN + DIR + DIV + DL + DT + EM + EMBED + FIELDSET + FONT + FORM + FRAME + FRAMESET + H + H1 + H2 + H3 + H4 + H5 + H6 + HEAD + HR + HTA:APPLICATION + HTML + I + IFRAME + IMG + INPUT + INS + ISINDEX + KBD + LABEL + LEGEnd + LI + LINK + LISTING + MAP + MARQUEE + MENU + META + MULTICOL + NEXTID + NOBR + NOFRAMES + NOSCRIPT + OBJECT + OL + OPTGROUP + OPTION + P + PARAM + PLAINTEXT + PRE + Q + S + SAMP + SCRIPT + SELECT + SERVER + SMALL + SOUND + SPACER + Span + STRONG + STYLE + SUB + SUP + TABLE + TBODY + TD + TEXTAREA + TEXTFLOW + TFOOT + TH + THEAD + TITLE + TR + TT + U + VAR + WBR + XMP + + + abbr + accept-charset + accept + accesskey + action + align + alink + alt + applicationname + archive + axis + background + behavior + bgcolor + bgproperties + border + bordercolor + bordercolordark + bordercolorligh + borderstyle + caption + cellpadding + cellspacing + char + charoff + charset + checked + cite + class + classid + clear + code + codetype + color + cols + colspan + compact + content + coords + data + datetime + declare + defer + dir + direction + disabled + dynsrc + enctype + face + for + frame + frameborder + framespacing + gutter + headers + height + href + hreflang + hspace + http-equiv + icon + id + ismap + label + language + leftmargin + link + longdesc + loop + lowsrc + marginheight + marginwidth + maximizebutton + maxlength + media + method + methods + minimizebutton + multiple + name + nohref + noresize + noshade + nowrap + object + onabort + onblur + onchange + onclick + ondblclick + onerror + onfocus + onkeydown + onkeypress + onkeyup + onload + onmousedown + onmousemove + onmouseout + onmouseover + onmouseup + onreset + onselect + onsubmit + onunload + profile + prompt + readonly + rel + rev + rows + rowspan + rules + runat + scheme + scope + scrollamount + scrolldelay + scrolling + selected + shape + showintaskbar + singleinstance + size + span + src + standby + start + style + summary + sysmenu + tabindex + target + text + title + topmargin + type + urn + usemap + valign + value + valuetype + version + vlink + vrml + vspace + width + windowstate + wrap + + + " + " + + + ' + ' + + [\d\w_]+(?=(\s*=)) + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Java-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Java-Mode.xshd new file mode 100644 index 000000000..dd5053e91 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Java-Mode.xshd @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + [?,.()\[\]{}+\-/%*<>^!|]+ + + + this + super + + + new + instanceof + true + false + + + else + if + switch + case + + + do + for + while + + + break + continue + default + goto + return + + + try + throw + catch + finally + + + boolean + double + int + short + long + float + byte + char + + + class + interface + object + + + void + + + abstract + const + static + final + native + extends + implements + volatile + transient + throws + strictfp + synchronized + + + public + protected + private + + + package + import + + + null + + + // + + + /\* + \*/ + + + " + " + + + + + + ' + ' + + + + + [\d\w_]+(?=(\s*\()) + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + + + TODO + + + @author + @version + @param + @return + @exception + @throws + @see + @since + @serial + @serialField + @serialData + @deprecated + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/JavaScript-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/JavaScript-Mode.xshd new file mode 100644 index 000000000..97775dce5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/JavaScript-Mode.xshd @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + break + continue + delete + else + for + function + if + in + new + return + this + typeof + var + void + while + with + abstract + boolean + byte + case + catch + char + class + const + debugger + default + do + double + enum + export + extends + final + finally + float + goto + implements + import + instanceof + int + interface + long + native + package + private + protected + public + short + static + super + switch + synchronized + throw + throws + transient + try + volatile + + + Array + Boolean + Date + Function + Global + Math + Number + Object + RegExp + String + + + false + null + true + NaN + Infinity + + + eval + parseInt + parseFloat + escape + unescape + isNaN + isFinite + + + // + + + /\* + \*/ + + + + / + / + + + + + + " + " + + + + + + ' + ' + + + + + \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/MarkDown-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/MarkDown-Mode.xshd new file mode 100644 index 000000000..ead5045ab --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/MarkDown-Mode.xshd @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + ^\#.* + + + \*\*.*\*\* + + + __.*__ + + + \*(?![ ]).*\* + + + _.*_ + + + `.*` + + + ^\t + ^(?!\t) + + + ^[ ]{4} + ^(?![ ]{4}) + + + ^> + ^(?!>) + + + \!\[.*\]\[.*\] + + + \[.*\]\(.*\) + + + \[.*\]\[.*\] + + + [ ]{2}$ + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV1.xsd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV1.xsd new file mode 100644 index 000000000..82bce451d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV1.xsd @@ -0,0 +1,296 @@ + + + + + This schema defines the syntax for mode definitions in SharpDevelop. + The schema can be simplified quite a bit but it does the job as is. + + + If you are using this file as a reference it is probably easiest to scroll to + the botton to find the definition of the root element called SyntaxDefinition and + then unwind the different type definitions and refernces. + + Note on coloring: + Many tags define how some symbol should be colored. If a specific symbol + can not be matched onto either a Span definition, Keyword, or a Digit/Number it + will be rendered in the current default color. Which is the default color of the + current span or the default color of the mode as a whole if no span has been entered. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV2.xsd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV2.xsd new file mode 100644 index 000000000..047ef38a1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/ModeV2.xsd @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PHP-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PHP-Mode.xshd new file mode 100644 index 000000000..f09ff27d9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PHP-Mode.xshd @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + \# + + + + // + + + + /\* + \*/ + + + + + \b0[xX][0-9a-fA-F]+ # hex number + | + \b0[0-9]+ # octal number + | + ( \b\d+(\.[0-9]+)? #number with optional floating point + | \.[0-9]+ #or just starting with floating point + ) + ([eE][+-]?[0-9]+)? # optional exponent + + + + [?,.:;()\[\]{}+\-/%*<>&^!|~@]+ + + + + + \b + [\d\w_]+ # an identifier + (?=\s*\() # followed by ( + + + + ' + ' + + + + + + + + " + " + + + + + + + + + <<<\"?[\d\w_]+\"?$ + ^[\d\w_]+; + + + + + <<<\'[\d\w_]+\'$ + ^[\d\w_]+; + + + + global + my + var + + + + and + or + new + clone + instanceof + xor + true + false + + + + else + else + switch + case + endif + elseif + + + + do + for + foreach + while + endwhile + exit + + + + break + continue + default + goto + return + + + + require + include + require + include + function + + + + int + integer + real + double + float + string + array + object + + + + class + void + + + + public + private + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Patch-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Patch-Mode.xshd new file mode 100644 index 000000000..c8e1c3cfd --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Patch-Mode.xshd @@ -0,0 +1,35 @@ + + + + + + + + + + + Index:\s + + + == + + + --- + + + \+\+\+ + + + @@ + + + - + + + \+ + + + \s + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PowerShell.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PowerShell.xshd new file mode 100644 index 000000000..ee6fec4c8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/PowerShell.xshd @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + \# + + + + <\# + \#> + + + + " + " + + + + + + + + ' + ' + + + + + + + + @" + "@ + + + + + + + + while + param + end + define + else + from + foreach + var + dynamicparam + filter + dp + until + for + exit + switch + process + begin + elseif + if + in + data + class + using + function + + + + catch + finally + throw + trap + try + + + + break + continue + return + + + + class + + + + -not + -band + -bor + -replace + -ireplace + -creplace + -and + -or + -is + -isnot + -as + -lt + -le + -gt + -ge + -eq + -ne + -contains + -notcontains + -like + -notlike + -match + -notmatch + + + + \$[\d\w_]+ + + + + [\w]+-[\w]+ + + + + + \b0[xX][0-9a-fA-F]+ # hex number + | + ( \b\d+(\.[0-9]+)? #number with optional floating point + | \.[0-9]+ #or just starting with floating point + ) + ([eE][+-]?[0-9]+)? # optional exponent + + + + [?,.;()\[\]{}+\-/%*<>^+~!|&]+ + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Resources.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Resources.cs new file mode 100644 index 000000000..3ac838ce3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Resources.cs @@ -0,0 +1,48 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; + +namespace Tango.Scripting.Editors.Highlighting +{ + static class Resources + { + static readonly string Prefix = typeof(Resources).FullName + "."; + + public static Stream OpenStream(string name) + { + Stream s = typeof(Resources).Assembly.GetManifestResourceStream(Prefix + name); + if (s == null) + throw new FileNotFoundException("The resource file '" + name + "' was not found."); + return s; + } + + internal static void RegisterBuiltInHighlightings(HighlightingManager.DefaultHighlightingManager hlm) + { + hlm.RegisterHighlighting("XmlDoc", null, "XmlDoc.xshd"); + hlm.RegisterHighlighting("C#", new[] { ".cs" }, "CSharp-Mode.xshd"); + + hlm.RegisterHighlighting("JavaScript", new[] { ".js" }, "JavaScript-Mode.xshd"); + hlm.RegisterHighlighting("HTML", new[] { ".htm", ".html" }, "HTML-Mode.xshd"); + hlm.RegisterHighlighting("ASP/XHTML", new[] { ".asp", ".aspx", ".asax", ".asmx", ".ascx", ".master" }, "ASPX.xshd"); + + hlm.RegisterHighlighting("Boo", new[] { ".boo" }, "Boo.xshd"); + hlm.RegisterHighlighting("Coco", new[] { ".atg" }, "Coco-Mode.xshd"); + hlm.RegisterHighlighting("CSS", new[] { ".css" }, "CSS-Mode.xshd"); + hlm.RegisterHighlighting("C++", new[] { ".c", ".h", ".cc", ".cpp" , ".hpp" }, "CPP-Mode.xshd"); + hlm.RegisterHighlighting("Java", new[] { ".java" }, "Java-Mode.xshd"); + hlm.RegisterHighlighting("Patch", new[] { ".patch", ".diff" }, "Patch-Mode.xshd"); + hlm.RegisterHighlighting("PowerShell", new[] { ".ps1", ".psm1", ".psd1" }, "PowerShell.xshd"); + hlm.RegisterHighlighting("PHP", new[] { ".php" }, "PHP-Mode.xshd"); + hlm.RegisterHighlighting("TeX", new[] { ".tex" }, "Tex-Mode.xshd"); + hlm.RegisterHighlighting("VBNET", new[] { ".vb" }, "VBNET-Mode.xshd"); + hlm.RegisterHighlighting("XML", (".xml;.xsl;.xslt;.xsd;.manifest;.config;.addin;" + + ".xshd;.wxs;.wxi;.wxl;.proj;.csproj;.vbproj;.ilproj;" + + ".booproj;.build;.xfrm;.targets;.xaml;.xpt;" + + ".xft;.map;.wsdl;.disco;.ps1xml;.nuspec").Split(';'), + "XML-Mode.xshd"); + hlm.RegisterHighlighting("MarkDown", new[] { ".md" }, "MarkDown-Mode.xshd"); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Tex-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Tex-Mode.xshd new file mode 100644 index 000000000..91083b0ab --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/Tex-Mode.xshd @@ -0,0 +1,108 @@ + + + + + + + + + + &~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + % + + + + $$ + $$ + + + \[ + \] + + + + \ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + + % + + + + \ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/VBNET-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/VBNET-Mode.xshd new file mode 100644 index 000000000..b22555a29 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/VBNET-Mode.xshd @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + " + " + + + + + + (?<=(^\s*))\# + + + (?<!(^\s*))\# + \# + + + ''' + + + ' + + + \bREM\b + + + Boolean + Byte + Char + Date + Decimal + Double + Integer + Long + Object + SByte + Short + Single + String + UInteger + ULong + UShort + Variant + + + AddressOf + And + AndAlso + Await + Is + IsNot + Like + Mod + New + Not + Or + OrElse + Xor + + + False + Me + MyBase + MyClass + Nothing + True + + + CBool + CByte + CChar + CDate + CDbl + CDec + CInt + CLng + CObj + CSByte + CShort + CSng + CStr + CType + CUInt + CULng + CUShort + DirectCast + GetType + GetXmlNamespace + IIf + TryCast + TypeOf + + + AddHandler + Alias + As + ByRef + ByVal + Call + Case + Catch + Class + Const + Continue + Declare + Default + Delegate + Dim + Do + Each + Else + ElseIf + End + EndIf + Enum + Erase + Error + Event + Exit + Finally + For + Friend + Function + Get + Global + GoSub + GoTo + Handles + If + Implements + Imports + In + Inherits + Interface + Let + Lib + Loop + Module + MustInherit + MustOverride + Namespace + Narrowing + New + Next + NotInheritable + NotOverridable + Of + On + Operator + Option + Optional + Overloads + Overridable + Overrides + ParamArray + Partial + Private + Property + Protected + Public + RaiseEvent + ReadOnly + ReDim + RemoveHandler + Resume + Return + Select + Set + Shadows + Shared + Static + Step + Stop + Structure + Sub + SyncLock + Then + Throw + To + Try + Using + Wend + When + While + Widening + With + WithEvents + WriteOnly + + + Aggregate + Ansi + Ascending + Async + Auto + Binary + By + Compare + Custom + Descending + Distinct + Equals + Explicit + From + Group + Infer + Into + Iterator + Join + Key + Off + Preserve + Skip + Strict + Take + Text + Unicode + Until + Where + Yield + + + + + Const + Else + ElseIf + End + ExternalChecksum + ExternalSource + If + Region + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XML-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XML-Mode.xshd new file mode 100644 index 000000000..8f0bdef76 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XML-Mode.xshd @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + <!-- + --> + + + <!\[CDATA\[ + ]]> + + + <!DOCTYPE + > + + + <\? + \?> + + + < + > + + + + " + "|(?=<) + + + ' + '|(?=<) + + [\d\w_\-\.]+(?=(\s*=)) + = + + + + + + + + & + [\w\d\#]+ + ; + + + + & + [\w\d\#]* + #missing ; + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XmlDoc.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XmlDoc.xshd new file mode 100644 index 000000000..e4303de6a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/XmlDoc.xshd @@ -0,0 +1,57 @@ + + + + + + + + + + < + > + + + " + " + + + / + | + = + + + c + code + example + exception + list + para + param + paramref + permission + remarks + returns + see + seealso + summary + value + + type + name + cref + item + term + description + listheader + typeparam + typeparamref + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/HighlightingLoader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/HighlightingLoader.cs new file mode 100644 index 000000000..f1d0d8554 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/HighlightingLoader.cs @@ -0,0 +1,99 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Xml; +using System.Xml.Schema; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// Static class with helper methods to load XSHD highlighting files. + /// + public static class HighlightingLoader + { + #region XSHD loading + /// + /// Lodas a syntax definition from the xml reader. + /// + public static XshdSyntaxDefinition LoadXshd(XmlReader reader) + { + return LoadXshd(reader, false); + } + + internal static XshdSyntaxDefinition LoadXshd(XmlReader reader, bool skipValidation) + { + if (reader == null) + throw new ArgumentNullException("reader"); + try { + reader.MoveToContent(); + if (reader.NamespaceURI == V2Loader.Namespace) { + return V2Loader.LoadDefinition(reader, skipValidation); + } else { + return V1Loader.LoadDefinition(reader, skipValidation); + } + } catch (XmlSchemaException ex) { + throw WrapException(ex, ex.LineNumber, ex.LinePosition); + } catch (XmlException ex) { + throw WrapException(ex, ex.LineNumber, ex.LinePosition); + } + } + + static Exception WrapException(Exception ex, int lineNumber, int linePosition) + { + return new HighlightingDefinitionInvalidException(FormatExceptionMessage(ex.Message, lineNumber, linePosition), ex); + } + + internal static string FormatExceptionMessage(string message, int lineNumber, int linePosition) + { + if (lineNumber <= 0) + return message; + else + return "Error at position (line " + lineNumber + ", column " + linePosition + "):\n" + message; + } + + internal static XmlReader GetValidatingReader(XmlReader input, bool ignoreWhitespace, XmlSchemaSet schemaSet) + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.CloseInput = true; + settings.IgnoreComments = true; + settings.IgnoreWhitespace = ignoreWhitespace; + if (schemaSet != null) { + settings.Schemas = schemaSet; + settings.ValidationType = ValidationType.Schema; + } + return XmlReader.Create(input, settings); + } + + internal static XmlSchemaSet LoadSchemaSet(XmlReader schemaInput) + { + XmlSchemaSet schemaSet = new XmlSchemaSet(); + schemaSet.Add(null, schemaInput); + schemaSet.ValidationEventHandler += delegate(object sender, ValidationEventArgs args) { + throw new HighlightingDefinitionInvalidException(args.Message); + }; + return schemaSet; + } + #endregion + + #region Load Highlighting from XSHD + /// + /// Creates a highlighting definition from the XSHD file. + /// + public static IHighlightingDefinition Load(XshdSyntaxDefinition syntaxDefinition, IHighlightingDefinitionReferenceResolver resolver) + { + if (syntaxDefinition == null) + throw new ArgumentNullException("syntaxDefinition"); + return new XmlHighlightingDefinition(syntaxDefinition, resolver); + } + + /// + /// Creates a highlighting definition from the XSHD file. + /// + public static IHighlightingDefinition Load(XmlReader reader, IHighlightingDefinitionReferenceResolver resolver) + { + return Load(LoadXshd(reader), resolver); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/IXshdVisitor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/IXshdVisitor.cs new file mode 100644 index 000000000..f328a32d3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/IXshdVisitor.cs @@ -0,0 +1,32 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A visitor over the XSHD element tree. + /// + public interface IXshdVisitor + { + /// + object VisitRuleSet(XshdRuleSet ruleSet); + + /// + object VisitColor(XshdColor color); + + /// + object VisitKeywords(XshdKeywords keywords); + + /// + object VisitSpan(XshdSpan span); + + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", Justification = "A VB programmer implementing a visitor?")] + object VisitImport(XshdImport import); + + /// + object VisitRule(XshdRule rule); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/SaveXshdVisitor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/SaveXshdVisitor.cs new file mode 100644 index 000000000..e158954f1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/SaveXshdVisitor.cs @@ -0,0 +1,182 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Xml; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// Xshd visitor implementation that saves an .xshd file as XML. + /// + public sealed class SaveXshdVisitor : IXshdVisitor + { + /// + /// XML namespace for XSHD. + /// + public const string Namespace = V2Loader.Namespace; + + XmlWriter writer; + + /// + /// Creates a new SaveXshdVisitor instance. + /// + public SaveXshdVisitor(XmlWriter writer) + { + if (writer == null) + throw new ArgumentNullException("writer"); + this.writer = writer; + } + + /// + /// Writes the specified syntax definition. + /// + public void WriteDefinition(XshdSyntaxDefinition definition) + { + if (definition == null) + throw new ArgumentNullException("definition"); + writer.WriteStartElement("SyntaxDefinition", Namespace); + if (definition.Name != null) + writer.WriteAttributeString("name", definition.Name); + if (definition.Extensions != null) + writer.WriteAttributeString("extensions", string.Join(";", definition.Extensions.ToArray())); + + definition.AcceptElements(this); + + writer.WriteEndElement(); + } + + object IXshdVisitor.VisitRuleSet(XshdRuleSet ruleSet) + { + writer.WriteStartElement("RuleSet", Namespace); + + if (ruleSet.Name != null) + writer.WriteAttributeString("name", ruleSet.Name); + WriteBoolAttribute("ignoreCase", ruleSet.IgnoreCase); + + ruleSet.AcceptElements(this); + + writer.WriteEndElement(); + return null; + } + + void WriteBoolAttribute(string attributeName, bool? value) + { + if (value != null) { + writer.WriteAttributeString(attributeName, value.Value ? "true" : "false"); + } + } + + void WriteRuleSetReference(XshdReference ruleSetReference) + { + if (ruleSetReference.ReferencedElement != null) { + if (ruleSetReference.ReferencedDefinition != null) + writer.WriteAttributeString("ruleSet", ruleSetReference.ReferencedDefinition + "/" + ruleSetReference.ReferencedElement); + else + writer.WriteAttributeString("ruleSet", ruleSetReference.ReferencedElement); + } + } + + void WriteColorReference(XshdReference color) + { + if (color.InlineElement != null) { + WriteColorAttributes(color.InlineElement); + } else if (color.ReferencedElement != null) { + if (color.ReferencedDefinition != null) + writer.WriteAttributeString("color", color.ReferencedDefinition + "/" + color.ReferencedElement); + else + writer.WriteAttributeString("color", color.ReferencedElement); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The file format requires lowercase, and all possible values are English-only")] + void WriteColorAttributes(XshdColor color) + { + if (color.Foreground != null) + writer.WriteAttributeString("foreground", color.Foreground.ToString()); + if (color.Background != null) + writer.WriteAttributeString("background", color.Background.ToString()); + if (color.FontWeight != null) + writer.WriteAttributeString("fontWeight", V2Loader.FontWeightConverter.ConvertToInvariantString(color.FontWeight.Value).ToLowerInvariant()); + if (color.FontStyle != null) + writer.WriteAttributeString("fontStyle", V2Loader.FontStyleConverter.ConvertToInvariantString(color.FontStyle.Value).ToLowerInvariant()); + } + + object IXshdVisitor.VisitColor(XshdColor color) + { + writer.WriteStartElement("Color", Namespace); + if (color.Name != null) + writer.WriteAttributeString("name", color.Name); + WriteColorAttributes(color); + if (color.ExampleText != null) + writer.WriteAttributeString("exampleText", color.ExampleText); + writer.WriteEndElement(); + return null; + } + + object IXshdVisitor.VisitKeywords(XshdKeywords keywords) + { + writer.WriteStartElement("Keywords", Namespace); + WriteColorReference(keywords.ColorReference); + foreach (string word in keywords.Words) { + writer.WriteElementString("Word", Namespace, word); + } + writer.WriteEndElement(); + return null; + } + + object IXshdVisitor.VisitSpan(XshdSpan span) + { + writer.WriteStartElement("Span", Namespace); + WriteColorReference(span.SpanColorReference); + if (span.BeginRegexType == XshdRegexType.Default && span.BeginRegex != null) + writer.WriteAttributeString("begin", span.BeginRegex); + if (span.EndRegexType == XshdRegexType.Default && span.EndRegex != null) + writer.WriteAttributeString("end", span.EndRegex); + WriteRuleSetReference(span.RuleSetReference); + if (span.Multiline) + writer.WriteAttributeString("multiline", "true"); + + if (span.BeginRegexType == XshdRegexType.IgnorePatternWhitespace) + WriteBeginEndElement("Begin", span.BeginRegex, span.BeginColorReference); + if (span.EndRegexType == XshdRegexType.IgnorePatternWhitespace) + WriteBeginEndElement("End", span.EndRegex, span.EndColorReference); + + if (span.RuleSetReference.InlineElement != null) + span.RuleSetReference.InlineElement.AcceptVisitor(this); + + writer.WriteEndElement(); + return null; + } + + void WriteBeginEndElement(string elementName, string regex, XshdReference colorReference) + { + if (regex != null) { + writer.WriteStartElement(elementName, Namespace); + WriteColorReference(colorReference); + writer.WriteString(regex); + writer.WriteEndElement(); + } + } + + object IXshdVisitor.VisitImport(XshdImport import) + { + writer.WriteStartElement("Import", Namespace); + WriteRuleSetReference(import.RuleSetReference); + writer.WriteEndElement(); + return null; + } + + object IXshdVisitor.VisitRule(XshdRule rule) + { + writer.WriteStartElement("Rule", Namespace); + WriteColorReference(rule.ColorReference); + + writer.WriteString(rule.Regex); + + writer.WriteEndElement(); + return null; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/V1Loader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/V1Loader.cs new file mode 100644 index 000000000..f3caa7eda --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/V1Loader.cs @@ -0,0 +1,325 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Media; +using System.Xml; +using System.Xml.Schema; + +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// Loads .xshd files, version 1.0. + /// + sealed class V1Loader + { + static XmlSchemaSet schemaSet; + + static XmlSchemaSet SchemaSet { + get { + if (schemaSet == null) { + schemaSet = HighlightingLoader.LoadSchemaSet(new XmlTextReader( + Resources.OpenStream("ModeV1.xsd"))); + } + return schemaSet; + } + } + + public static XshdSyntaxDefinition LoadDefinition(XmlReader reader, bool skipValidation) + { + reader = HighlightingLoader.GetValidatingReader(reader, false, skipValidation ? null : SchemaSet); + XmlDocument document = new XmlDocument(); + document.Load(reader); + V1Loader loader = new V1Loader(); + return loader.ParseDefinition(document.DocumentElement); + } + + XshdSyntaxDefinition ParseDefinition(XmlElement syntaxDefinition) + { + XshdSyntaxDefinition def = new XshdSyntaxDefinition(); + def.Name = syntaxDefinition.GetAttributeOrNull("name"); + if (syntaxDefinition.HasAttribute("extensions")) { + def.Extensions.AddRange(syntaxDefinition.GetAttribute("extensions").Split(';', '|')); + } + + XshdRuleSet mainRuleSetElement = null; + foreach (XmlElement element in syntaxDefinition.GetElementsByTagName("RuleSet")) { + XshdRuleSet ruleSet = ImportRuleSet(element); + def.Elements.Add(ruleSet); + if (ruleSet.Name == null) + mainRuleSetElement = ruleSet; + + if (syntaxDefinition["Digits"] != null) { + // create digit highlighting rule + + const string optionalExponent = @"([eE][+-]?[0-9]+)?"; + const string floatingPoint = @"\.[0-9]+"; + ruleSet.Elements.Add( + new XshdRule { + ColorReference = GetColorReference(syntaxDefinition["Digits"]), + RegexType = XshdRegexType.IgnorePatternWhitespace, + Regex = @"\b0[xX][0-9a-fA-F]+" + + @"|" + + @"(\b\d+(" + floatingPoint + ")?" + + @"|" + floatingPoint + ")" + + optionalExponent + }); + } + } + + if (syntaxDefinition.HasAttribute("extends") && mainRuleSetElement != null) { + // convert 'extends="HTML"' to '' in main rule set. + mainRuleSetElement.Elements.Add( + new XshdImport { RuleSetReference = new XshdReference( + syntaxDefinition.GetAttribute("extends"), string.Empty + ) }); + } + return def; + } + + static XshdColor GetColorFromElement(XmlElement element) + { + if (!element.HasAttribute("bold") && !element.HasAttribute("italic") && !element.HasAttribute("color") && !element.HasAttribute("bgcolor")) + return null; + XshdColor color = new XshdColor(); + if (element.HasAttribute("bold")) + color.FontWeight = XmlConvert.ToBoolean(element.GetAttribute("bold")) ? FontWeights.Bold : FontWeights.Normal; + if (element.HasAttribute("italic")) + color.FontStyle = XmlConvert.ToBoolean(element.GetAttribute("italic")) ? FontStyles.Italic : FontStyles.Normal; + if (element.HasAttribute("color")) + color.Foreground = ParseColor(element.GetAttribute("color")); + if (element.HasAttribute("bgcolor")) + color.Background = ParseColor(element.GetAttribute("bgcolor")); + return color; + } + + static XshdReference GetColorReference(XmlElement element) + { + XshdColor color = GetColorFromElement(element); + if (color != null) + return new XshdReference(color); + else + return new XshdReference(); + } + + static HighlightingBrush ParseColor(string c) + { + if (c.StartsWith("#", StringComparison.Ordinal)) { + int a = 255; + int offset = 0; + if (c.Length > 7) { + offset = 2; + a = Int32.Parse(c.Substring(1,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + int r = Int32.Parse(c.Substring(1 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + int g = Int32.Parse(c.Substring(3 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + int b = Int32.Parse(c.Substring(5 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + return new SimpleHighlightingBrush(Color.FromArgb((byte)a, (byte)r, (byte)g, (byte)b)); + } else if (c.StartsWith("SystemColors.", StringComparison.Ordinal)) { + return V2Loader.GetSystemColorBrush(null, c); + } else { + return new SimpleHighlightingBrush((Color)V2Loader.ColorConverter.ConvertFromInvariantString(c)); + } + } + + char ruleSetEscapeCharacter; + + XshdRuleSet ImportRuleSet(XmlElement element) + { + XshdRuleSet ruleSet = new XshdRuleSet(); + ruleSet.Name = element.GetAttributeOrNull("name"); + + if (element.HasAttribute("escapecharacter")) { + ruleSetEscapeCharacter = element.GetAttribute("escapecharacter")[0]; + } else { + ruleSetEscapeCharacter = '\0'; + } + + if (element.HasAttribute("reference")) { + ruleSet.Elements.Add( + new XshdImport { RuleSetReference = new XshdReference( + element.GetAttribute("reference"), string.Empty + ) }); + } + ruleSet.IgnoreCase = element.GetBoolAttribute("ignorecase"); + + foreach (XmlElement el in element.GetElementsByTagName("KeyWords")) { + XshdKeywords keywords = new XshdKeywords(); + keywords.ColorReference = GetColorReference(el); + // we have to handle old syntax highlighting definitions that contain + // empty keywords or empty keyword groups + foreach (XmlElement node in el.GetElementsByTagName("Key")) { + string word = node.GetAttribute("word"); + if (!string.IsNullOrEmpty(word)) + keywords.Words.Add(word); + } + if (keywords.Words.Count > 0) { + ruleSet.Elements.Add(keywords); + } + } + + foreach (XmlElement el in element.GetElementsByTagName("Span")) { + ruleSet.Elements.Add(ImportSpan(el)); + } + + foreach (XmlElement el in element.GetElementsByTagName("MarkPrevious")) { + ruleSet.Elements.Add(ImportMarkPrevNext(el, false)); + } + foreach (XmlElement el in element.GetElementsByTagName("MarkFollowing")) { + ruleSet.Elements.Add(ImportMarkPrevNext(el, true)); + } + + return ruleSet; + } + + static XshdRule ImportMarkPrevNext(XmlElement el, bool markFollowing) + { + bool markMarker = el.GetBoolAttribute("markmarker") ?? false; + string what = Regex.Escape(el.InnerText); + const string identifier = @"[\d\w_]+"; + const string whitespace = @"\s*"; + + string regex; + if (markFollowing) { + if (markMarker) { + regex = what + whitespace + identifier; + } else { + regex = "(?<=(" + what + whitespace + "))" + identifier; + } + } else { + if (markMarker) { + regex = identifier + whitespace + what; + } else { + regex = identifier + "(?=(" + whitespace + what + "))"; + } + } + return new XshdRule { + ColorReference = GetColorReference(el), + Regex = regex, + RegexType = XshdRegexType.IgnorePatternWhitespace + }; + } + + XshdSpan ImportSpan(XmlElement element) + { + XshdSpan span = new XshdSpan(); + if (element.HasAttribute("rule")) { + span.RuleSetReference = new XshdReference(null, element.GetAttribute("rule")); + } + char escapeCharacter = ruleSetEscapeCharacter; + if (element.HasAttribute("escapecharacter")) { + escapeCharacter = element.GetAttribute("escapecharacter")[0]; + } + span.Multiline = !(element.GetBoolAttribute("stopateol") ?? false); + + span.SpanColorReference = GetColorReference(element); + + span.BeginRegexType = XshdRegexType.IgnorePatternWhitespace; + span.BeginRegex = ImportRegex(element["Begin"].InnerText, + element["Begin"].GetBoolAttribute("singleword") ?? false, + element["Begin"].GetBoolAttribute("startofline")); + span.BeginColorReference = GetColorReference(element["Begin"]); + + string endElementText = string.Empty; + if (element["End"] != null) { + span.EndRegexType = XshdRegexType.IgnorePatternWhitespace; + endElementText = element["End"].InnerText; + span.EndRegex = ImportRegex(endElementText, + element["End"].GetBoolAttribute("singleword") ?? false, + null); + span.EndColorReference = GetColorReference(element["End"]); + } + + if (escapeCharacter != '\0') { + XshdRuleSet ruleSet = new XshdRuleSet(); + if (endElementText.Length == 1 && endElementText[0] == escapeCharacter) { + // ""-style escape + ruleSet.Elements.Add(new XshdSpan { + BeginRegex = Regex.Escape(endElementText + endElementText), + EndRegex = "" + }); + } else { + // \"-style escape + ruleSet.Elements.Add(new XshdSpan { + BeginRegex = Regex.Escape(escapeCharacter.ToString()), + EndRegex = "." + }); + } + if (span.RuleSetReference.ReferencedElement != null) { + ruleSet.Elements.Add(new XshdImport { RuleSetReference = span.RuleSetReference }); + } + span.RuleSetReference = new XshdReference(ruleSet); + } + return span; + } + + static string ImportRegex(string expr, bool singleWord, bool? startOfLine) + { + StringBuilder b = new StringBuilder(); + if (startOfLine != null) { + if (startOfLine.Value) { + b.Append(@"(?<=(^\s*))"); + } else { + b.Append(@"(? + /// Loads .xshd files, version 2.0. + /// Version 2.0 files are recognized by the namespace. + /// + static class V2Loader + { + public const string Namespace = "http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"; + + static XmlSchemaSet schemaSet; + + static XmlSchemaSet SchemaSet { + get { + if (schemaSet == null) { + schemaSet = HighlightingLoader.LoadSchemaSet(new XmlTextReader( + Resources.OpenStream("ModeV2.xsd"))); + } + return schemaSet; + } + } + + public static XshdSyntaxDefinition LoadDefinition(XmlReader reader, bool skipValidation) + { + reader = HighlightingLoader.GetValidatingReader(reader, true, skipValidation ? null : SchemaSet); + reader.Read(); + return ParseDefinition(reader); + } + + static XshdSyntaxDefinition ParseDefinition(XmlReader reader) + { + Debug.Assert(reader.LocalName == "SyntaxDefinition"); + XshdSyntaxDefinition def = new XshdSyntaxDefinition(); + def.Name = reader.GetAttribute("name"); + string extensions = reader.GetAttribute("extensions"); + if (extensions != null) + def.Extensions.AddRange(extensions.Split(';')); + ParseElements(def.Elements, reader); + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + Debug.Assert(reader.LocalName == "SyntaxDefinition"); + return def; + } + + static void ParseElements(ICollection c, XmlReader reader) + { + if (reader.IsEmptyElement) + return; + while (reader.Read() && reader.NodeType != XmlNodeType.EndElement) { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + if (reader.NamespaceURI != Namespace) { + if (!reader.IsEmptyElement) + reader.Skip(); + continue; + } + switch (reader.Name) { + case "RuleSet": + c.Add(ParseRuleSet(reader)); + break; + case "Property": + c.Add(ParseProperty(reader)); + break; + case "Color": + c.Add(ParseNamedColor(reader)); + break; + case "Keywords": + c.Add(ParseKeywords(reader)); + break; + case "Span": + c.Add(ParseSpan(reader)); + break; + case "Import": + c.Add(ParseImport(reader)); + break; + case "Rule": + c.Add(ParseRule(reader)); + break; + default: + throw new NotSupportedException("Unknown element " + reader.Name); + } + } + } + + static XshdElement ParseProperty(XmlReader reader) + { + XshdProperty property = new XshdProperty(); + SetPosition(property, reader); + property.Name = reader.GetAttribute("name"); + property.Value = reader.GetAttribute("value"); + return property; + } + + static XshdRuleSet ParseRuleSet(XmlReader reader) + { + XshdRuleSet ruleSet = new XshdRuleSet(); + SetPosition(ruleSet, reader); + ruleSet.Name = reader.GetAttribute("name"); + ruleSet.IgnoreCase = reader.GetBoolAttribute("ignoreCase"); + + CheckElementName(reader, ruleSet.Name); + ParseElements(ruleSet.Elements, reader); + return ruleSet; + } + + static XshdRule ParseRule(XmlReader reader) + { + XshdRule rule = new XshdRule(); + SetPosition(rule, reader); + rule.ColorReference = ParseColorReference(reader); + if (!reader.IsEmptyElement) { + reader.Read(); + if (reader.NodeType == XmlNodeType.Text) { + rule.Regex = reader.ReadContentAsString(); + rule.RegexType = XshdRegexType.IgnorePatternWhitespace; + } + } + return rule; + } + + static XshdKeywords ParseKeywords(XmlReader reader) + { + XshdKeywords keywords = new XshdKeywords(); + SetPosition(keywords, reader); + keywords.ColorReference = ParseColorReference(reader); + reader.Read(); + while (reader.NodeType != XmlNodeType.EndElement) { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + keywords.Words.Add(reader.ReadElementString()); + } + return keywords; + } + + static XshdImport ParseImport(XmlReader reader) + { + XshdImport import = new XshdImport(); + SetPosition(import, reader); + import.RuleSetReference = ParseRuleSetReference(reader); + if (!reader.IsEmptyElement) + reader.Skip(); + return import; + } + + static XshdSpan ParseSpan(XmlReader reader) + { + XshdSpan span = new XshdSpan(); + SetPosition(span, reader); + span.BeginRegex = reader.GetAttribute("begin"); + span.EndRegex = reader.GetAttribute("end"); + span.Multiline = reader.GetBoolAttribute("multiline") ?? false; + span.SpanColorReference = ParseColorReference(reader); + span.RuleSetReference = ParseRuleSetReference(reader); + if (!reader.IsEmptyElement) { + reader.Read(); + while (reader.NodeType != XmlNodeType.EndElement) { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + switch (reader.Name) { + case "Begin": + if (span.BeginRegex != null) + throw Error(reader, "Duplicate Begin regex"); + span.BeginColorReference = ParseColorReference(reader); + span.BeginRegex = reader.ReadElementString(); + span.BeginRegexType = XshdRegexType.IgnorePatternWhitespace; + break; + case "End": + if (span.EndRegex != null) + throw Error(reader, "Duplicate End regex"); + span.EndColorReference = ParseColorReference(reader); + span.EndRegex = reader.ReadElementString(); + span.EndRegexType = XshdRegexType.IgnorePatternWhitespace; + break; + case "RuleSet": + if (span.RuleSetReference.ReferencedElement != null) + throw Error(reader, "Cannot specify both inline RuleSet and RuleSet reference"); + span.RuleSetReference = new XshdReference(ParseRuleSet(reader)); + reader.Read(); + break; + default: + throw new NotSupportedException("Unknown element " + reader.Name); + } + } + } + return span; + } + + static Exception Error(XmlReader reader, string message) + { + return Error(reader as IXmlLineInfo, message); + } + + static Exception Error(IXmlLineInfo lineInfo, string message) + { + if (lineInfo != null) + return new HighlightingDefinitionInvalidException(HighlightingLoader.FormatExceptionMessage(message, lineInfo.LineNumber, lineInfo.LinePosition)); + else + return new HighlightingDefinitionInvalidException(message); + } + + /// + /// Sets the element's position to the XmlReader's position. + /// + static void SetPosition(XshdElement element, XmlReader reader) + { + IXmlLineInfo lineInfo = reader as IXmlLineInfo; + if (lineInfo != null) { + element.LineNumber = lineInfo.LineNumber; + element.ColumnNumber = lineInfo.LinePosition; + } + } + + static XshdReference ParseRuleSetReference(XmlReader reader) + { + string ruleSet = reader.GetAttribute("ruleSet"); + if (ruleSet != null) { + // '/' is valid in highlighting definition names, so we need the last occurence + int pos = ruleSet.LastIndexOf('/'); + if (pos >= 0) { + return new XshdReference(ruleSet.Substring(0, pos), ruleSet.Substring(pos + 1)); + } else { + return new XshdReference(null, ruleSet); + } + } else { + return new XshdReference(); + } + } + + static void CheckElementName(XmlReader reader, string name) + { + if (name != null) { + if (name.Length == 0) + throw Error(reader, "The empty string is not a valid name."); + if (name.IndexOf('/') >= 0) + throw Error(reader, "Element names must not contain a slash."); + } + } + + #region ParseColor + static XshdColor ParseNamedColor(XmlReader reader) + { + XshdColor color = ParseColorAttributes(reader); + // check removed: invisible named colors may be useful now that apps can read highlighting data + //if (color.Foreground == null && color.FontWeight == null && color.FontStyle == null) + // throw Error(reader, "A named color must have at least one element."); + color.Name = reader.GetAttribute("name"); + CheckElementName(reader, color.Name); + color.ExampleText = reader.GetAttribute("exampleText"); + return color; + } + + static XshdReference ParseColorReference(XmlReader reader) + { + string color = reader.GetAttribute("color"); + if (color != null) { + int pos = color.LastIndexOf('/'); + if (pos >= 0) { + return new XshdReference(color.Substring(0, pos), color.Substring(pos + 1)); + } else { + return new XshdReference(null, color); + } + } else { + return new XshdReference(ParseColorAttributes(reader)); + } + } + + static XshdColor ParseColorAttributes(XmlReader reader) + { + XshdColor color = new XshdColor(); + SetPosition(color, reader); + IXmlLineInfo position = reader as IXmlLineInfo; + color.Foreground = ParseColor(position, reader.GetAttribute("foreground")); + color.Background = ParseColor(position, reader.GetAttribute("background")); + color.FontWeight = ParseFontWeight(reader.GetAttribute("fontWeight")); + color.FontStyle = ParseFontStyle(reader.GetAttribute("fontStyle")); + return color; + } + + internal readonly static ColorConverter ColorConverter = new ColorConverter(); + internal readonly static FontWeightConverter FontWeightConverter = new FontWeightConverter(); + internal readonly static FontStyleConverter FontStyleConverter = new FontStyleConverter(); + + static HighlightingBrush ParseColor(IXmlLineInfo lineInfo, string color) + { + if (string.IsNullOrEmpty(color)) + return null; + if (color.StartsWith("SystemColors.", StringComparison.Ordinal)) + return GetSystemColorBrush(lineInfo, color); + else + return FixedColorHighlightingBrush((Color?)ColorConverter.ConvertFromInvariantString(color)); + } + + internal static SystemColorHighlightingBrush GetSystemColorBrush(IXmlLineInfo lineInfo, string name) + { + Debug.Assert(name.StartsWith("SystemColors.", StringComparison.Ordinal)); + string shortName = name.Substring(13); + var property = typeof(SystemColors).GetProperty(shortName + "Brush"); + if (property == null) + throw Error(lineInfo, "Cannot find '" + name + "'."); + return new SystemColorHighlightingBrush(property); + } + + static HighlightingBrush FixedColorHighlightingBrush(Color? color) + { + if (color == null) + return null; + return new SimpleHighlightingBrush(color.Value); + } + + static FontWeight? ParseFontWeight(string fontWeight) + { + if (string.IsNullOrEmpty(fontWeight)) + return null; + return (FontWeight?)FontWeightConverter.ConvertFromInvariantString(fontWeight); + } + + static FontStyle? ParseFontStyle(string fontStyle) + { + if (string.IsNullOrEmpty(fontStyle)) + return null; + return (FontStyle?)FontStyleConverter.ConvertFromInvariantString(fontStyle); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XmlHighlightingDefinition.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XmlHighlightingDefinition.cs new file mode 100644 index 000000000..3f52e5d87 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XmlHighlightingDefinition.cs @@ -0,0 +1,406 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + [Serializable] + sealed class XmlHighlightingDefinition : IHighlightingDefinition2 + { + public string Name { get; private set; } + + public XmlHighlightingDefinition(XshdSyntaxDefinition xshd, IHighlightingDefinitionReferenceResolver resolver) + { + this.Name = xshd.Name; + // Create HighlightingRuleSet instances + var rnev = new RegisterNamedElementsVisitor(this); + xshd.AcceptElements(rnev); + // Assign MainRuleSet so that references can be resolved + foreach (XshdElement element in xshd.Elements) { + XshdRuleSet xrs = element as XshdRuleSet; + if (xrs != null && xrs.Name == null) { + if (MainRuleSet != null) + throw Error(element, "Duplicate main RuleSet. There must be only one nameless RuleSet!"); + else + MainRuleSet = rnev.ruleSets[xrs]; + } + } + if (MainRuleSet == null) + throw new HighlightingDefinitionInvalidException("Could not find main RuleSet."); + // Translate elements within the rulesets (resolving references and processing imports) + xshd.AcceptElements(new TranslateElementVisitor(this, rnev.ruleSets, resolver)); + + foreach (var p in xshd.Elements.OfType()) + propDict.Add(p.Name, p.Value); + } + + #region RegisterNamedElements + sealed class RegisterNamedElementsVisitor : IXshdVisitor + { + XmlHighlightingDefinition def; + internal readonly Dictionary ruleSets + = new Dictionary(); + + public RegisterNamedElementsVisitor(XmlHighlightingDefinition def) + { + this.def = def; + } + + public object VisitRuleSet(XshdRuleSet ruleSet) + { + HighlightingRuleSet hrs = new HighlightingRuleSet(); + ruleSets.Add(ruleSet, hrs); + if (ruleSet.Name != null) { + if (ruleSet.Name.Length == 0) + throw Error(ruleSet, "Name must not be the empty string"); + if (def.ruleSetDict.ContainsKey(ruleSet.Name)) + throw Error(ruleSet, "Duplicate rule set name '" + ruleSet.Name + "'."); + + def.ruleSetDict.Add(ruleSet.Name, hrs); + } + ruleSet.AcceptElements(this); + return null; + } + + public object VisitColor(XshdColor color) + { + if (color.Name != null) { + if (color.Name.Length == 0) + throw Error(color, "Name must not be the empty string"); + if (def.colorDict.ContainsKey(color.Name)) + throw Error(color, "Duplicate color name '" + color.Name + "'."); + + def.colorDict.Add(color.Name, new HighlightingColor()); + } + return null; + } + + public object VisitKeywords(XshdKeywords keywords) + { + return keywords.ColorReference.AcceptVisitor(this); + } + + public object VisitSpan(XshdSpan span) + { + span.BeginColorReference.AcceptVisitor(this); + span.SpanColorReference.AcceptVisitor(this); + span.EndColorReference.AcceptVisitor(this); + return span.RuleSetReference.AcceptVisitor(this); + } + + public object VisitImport(XshdImport import) + { + return import.RuleSetReference.AcceptVisitor(this); + } + + public object VisitRule(XshdRule rule) + { + return rule.ColorReference.AcceptVisitor(this); + } + } + #endregion + + #region TranslateElements + sealed class TranslateElementVisitor : IXshdVisitor + { + readonly XmlHighlightingDefinition def; + readonly Dictionary ruleSetDict; + readonly Dictionary reverseRuleSetDict; + readonly IHighlightingDefinitionReferenceResolver resolver; + HashSet processingStartedRuleSets = new HashSet(); + HashSet processedRuleSets = new HashSet(); + bool ignoreCase; + + public TranslateElementVisitor(XmlHighlightingDefinition def, Dictionary ruleSetDict, IHighlightingDefinitionReferenceResolver resolver) + { + Debug.Assert(def != null); + Debug.Assert(ruleSetDict != null); + this.def = def; + this.ruleSetDict = ruleSetDict; + this.resolver = resolver; + reverseRuleSetDict = new Dictionary(); + foreach (var pair in ruleSetDict) { + reverseRuleSetDict.Add(pair.Value, pair.Key); + } + } + + public object VisitRuleSet(XshdRuleSet ruleSet) + { + HighlightingRuleSet rs = ruleSetDict[ruleSet]; + if (processedRuleSets.Contains(ruleSet)) + return rs; + if (!processingStartedRuleSets.Add(ruleSet)) + throw Error(ruleSet, "RuleSet cannot be processed because it contains cyclic "); + + bool oldIgnoreCase = ignoreCase; + if (ruleSet.IgnoreCase != null) + ignoreCase = ruleSet.IgnoreCase.Value; + + rs.Name = ruleSet.Name; + + foreach (XshdElement element in ruleSet.Elements) { + object o = element.AcceptVisitor(this); + HighlightingRuleSet elementRuleSet = o as HighlightingRuleSet; + if (elementRuleSet != null) { + Merge(rs, elementRuleSet); + } else { + HighlightingSpan span = o as HighlightingSpan; + if (span != null) { + rs.Spans.Add(span); + } else { + HighlightingRule elementRule = o as HighlightingRule; + if (elementRule != null) { + rs.Rules.Add(elementRule); + } + } + } + } + + ignoreCase = oldIgnoreCase; + processedRuleSets.Add(ruleSet); + + return rs; + } + + static void Merge(HighlightingRuleSet target, HighlightingRuleSet source) + { + target.Rules.AddRange(source.Rules); + target.Spans.AddRange(source.Spans); + } + + public object VisitColor(XshdColor color) + { + HighlightingColor c; + if (color.Name != null) + c = def.colorDict[color.Name]; + else if (color.Foreground == null && color.FontStyle == null && color.FontWeight == null) + return null; + else + c = new HighlightingColor(); + + c.Name = color.Name; + c.Foreground = color.Foreground; + c.Background = color.Background; + c.FontStyle = color.FontStyle; + c.FontWeight = color.FontWeight; + return c; + } + + public object VisitKeywords(XshdKeywords keywords) + { + if (keywords.Words.Count == 0) + return Error(keywords, "Keyword group must not be empty."); + foreach (string keyword in keywords.Words) { + if (string.IsNullOrEmpty(keyword)) + throw Error(keywords, "Cannot use empty string as keyword"); + } + StringBuilder keyWordRegex = new StringBuilder(); + // We can use "\b" only where the keyword starts/ends with a letter or digit, otherwise we don't + // highlight correctly. (example: ILAsm-Mode.xshd with ".maxstack" keyword) + if (keywords.Words.All(IsSimpleWord)) { + keyWordRegex.Append(@"\b(?>"); + // (?> = atomic group + // atomic groups increase matching performance, but we + // must ensure that the keywords are sorted correctly. + // "\b(?>in|int)\b" does not match "int" because the atomic group captures "in". + // To solve this, we are sorting the keywords by descending length. + int i = 0; + foreach (string keyword in keywords.Words.OrderByDescending(w=>w.Length)) { + if (i++ > 0) + keyWordRegex.Append('|'); + keyWordRegex.Append(Regex.Escape(keyword)); + } + keyWordRegex.Append(@")\b"); + } else { + keyWordRegex.Append('('); + int i = 0; + foreach (string keyword in keywords.Words) { + if (i++ > 0) + keyWordRegex.Append('|'); + if (char.IsLetterOrDigit(keyword[0])) + keyWordRegex.Append(@"\b"); + keyWordRegex.Append(Regex.Escape(keyword)); + if (char.IsLetterOrDigit(keyword[keyword.Length - 1])) + keyWordRegex.Append(@"\b"); + } + keyWordRegex.Append(')'); + } + return new HighlightingRule { + Color = GetColor(keywords, keywords.ColorReference), + Regex = CreateRegex(keywords, keyWordRegex.ToString(), XshdRegexType.Default) + }; + } + + static bool IsSimpleWord(string word) + { + return char.IsLetterOrDigit(word[0]) && char.IsLetterOrDigit(word, word.Length - 1); + } + + Regex CreateRegex(XshdElement position, string regex, XshdRegexType regexType) + { + if (regex == null) + throw Error(position, "Regex missing"); + RegexOptions options = RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture; + if (regexType == XshdRegexType.IgnorePatternWhitespace) + options |= RegexOptions.IgnorePatternWhitespace; + if (ignoreCase) + options |= RegexOptions.IgnoreCase; + try { + return new Regex(regex, options); + } catch (ArgumentException ex) { + throw Error(position, ex.Message); + } + } + + HighlightingColor GetColor(XshdElement position, XshdReference colorReference) + { + if (colorReference.InlineElement != null) { + return (HighlightingColor)colorReference.InlineElement.AcceptVisitor(this); + } else if (colorReference.ReferencedElement != null) { + IHighlightingDefinition definition = GetDefinition(position, colorReference.ReferencedDefinition); + HighlightingColor color = definition.GetNamedColor(colorReference.ReferencedElement); + if (color == null) + throw Error(position, "Could not find color named '" + colorReference.ReferencedElement + "'."); + return color; + } else { + return null; + } + } + + IHighlightingDefinition GetDefinition(XshdElement position, string definitionName) + { + if (definitionName == null) + return def; + if (resolver == null) + throw Error(position, "Resolving references to other syntax definitions is not possible because the IHighlightingDefinitionReferenceResolver is null."); + IHighlightingDefinition d = resolver.GetDefinition(definitionName); + if (d == null) + throw Error(position, "Could not find definition with name '" + definitionName + "'."); + return d; + } + + HighlightingRuleSet GetRuleSet(XshdElement position, XshdReference ruleSetReference) + { + if (ruleSetReference.InlineElement != null) { + return (HighlightingRuleSet)ruleSetReference.InlineElement.AcceptVisitor(this); + } else if (ruleSetReference.ReferencedElement != null) { + IHighlightingDefinition definition = GetDefinition(position, ruleSetReference.ReferencedDefinition); + HighlightingRuleSet ruleSet = definition.GetNamedRuleSet(ruleSetReference.ReferencedElement); + if (ruleSet == null) + throw Error(position, "Could not find rule set named '" + ruleSetReference.ReferencedElement + "'."); + return ruleSet; + } else { + return null; + } + } + + public object VisitSpan(XshdSpan span) + { + string endRegex = span.EndRegex; + if (string.IsNullOrEmpty(span.BeginRegex) && string.IsNullOrEmpty(span.EndRegex)) + throw Error(span, "Span has no start/end regex."); + if (!span.Multiline) { + if (endRegex == null) + endRegex = "$"; + else if (span.EndRegexType == XshdRegexType.IgnorePatternWhitespace) + endRegex = "($|" + endRegex + "\n)"; + else + endRegex = "($|" + endRegex + ")"; + } + HighlightingColor wholeSpanColor = GetColor(span, span.SpanColorReference); + return new HighlightingSpan { + StartExpression = CreateRegex(span, span.BeginRegex, span.BeginRegexType), + EndExpression = CreateRegex(span, endRegex, span.EndRegexType), + RuleSet = GetRuleSet(span, span.RuleSetReference), + StartColor = GetColor(span, span.BeginColorReference), + SpanColor = wholeSpanColor, + EndColor = GetColor(span, span.EndColorReference), + SpanColorIncludesStart = true, + SpanColorIncludesEnd = true + }; + } + + public object VisitImport(XshdImport import) + { + HighlightingRuleSet hrs = GetRuleSet(import, import.RuleSetReference); + XshdRuleSet inputRuleSet; + if (reverseRuleSetDict.TryGetValue(hrs, out inputRuleSet)) { + // ensure the ruleset is processed before importing its members + if (VisitRuleSet(inputRuleSet) != hrs) + Debug.Fail("this shouldn't happen"); + } + return hrs; + } + + public object VisitRule(XshdRule rule) + { + return new HighlightingRule { + Color = GetColor(rule, rule.ColorReference), + Regex = CreateRegex(rule, rule.Regex, rule.RegexType) + }; + } + } + #endregion + + static Exception Error(XshdElement element, string message) + { + if (element.LineNumber > 0) + return new HighlightingDefinitionInvalidException( + "Error at line " + element.LineNumber + ":\n" + message); + else + return new HighlightingDefinitionInvalidException(message); + } + + Dictionary ruleSetDict = new Dictionary(); + Dictionary colorDict = new Dictionary(); + [OptionalField] + Dictionary propDict = new Dictionary(); + + public HighlightingRuleSet MainRuleSet { get; private set; } + + public HighlightingRuleSet GetNamedRuleSet(string name) + { + if (string.IsNullOrEmpty(name)) + return MainRuleSet; + HighlightingRuleSet r; + if (ruleSetDict.TryGetValue(name, out r)) + return r; + else + return null; + } + + public HighlightingColor GetNamedColor(string name) + { + HighlightingColor c; + if (colorDict.TryGetValue(name, out c)) + return c; + else + return null; + } + + public IEnumerable NamedHighlightingColors { + get { + return colorDict.Values; + } + } + + public override string ToString() + { + return this.Name; + } + + public IDictionary Properties { + get { + return propDict; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdColor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdColor.cs new file mode 100644 index 000000000..bbc5abb98 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdColor.cs @@ -0,0 +1,97 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Windows; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A color in an Xshd file. + /// + [Serializable] + public class XshdColor : XshdElement, ISerializable + { + /// + /// Gets/sets the name. + /// + public string Name { get; set; } + + /// + /// Gets/sets the foreground brush. + /// + public HighlightingBrush Foreground { get; set; } + + /// + /// Gets/sets the background brush. + /// + public HighlightingBrush Background { get; set; } + + /// + /// Gets/sets the font weight. + /// + public FontWeight? FontWeight { get; set; } + + /// + /// Gets/sets the font style. + /// + public FontStyle? FontStyle { get; set; } + + /// + /// Gets/Sets the example text that demonstrates where the color is used. + /// + public string ExampleText { get; set; } + + /// + /// Creates a new XshdColor instance. + /// + public XshdColor() + { + } + + /// + /// Deserializes an XshdColor. + /// + protected XshdColor(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException("info"); + this.Name = info.GetString("Name"); + this.Foreground = (HighlightingBrush)info.GetValue("Foreground", typeof(HighlightingBrush)); + this.Background = (HighlightingBrush)info.GetValue("Background", typeof(HighlightingBrush)); + if (info.GetBoolean("HasWeight")) + this.FontWeight = System.Windows.FontWeight.FromOpenTypeWeight(info.GetInt32("Weight")); + if (info.GetBoolean("HasStyle")) + this.FontStyle = (FontStyle?)new FontStyleConverter().ConvertFromInvariantString(info.GetString("Style")); + this.ExampleText = info.GetString("ExampleText"); + } + + /// + /// Serializes this XshdColor instance. + /// + [System.Security.SecurityCritical] + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException("info"); + info.AddValue("Name", this.Name); + info.AddValue("Foreground", this.Foreground); + info.AddValue("Background", this.Background); + info.AddValue("HasWeight", this.FontWeight.HasValue); + if (this.FontWeight.HasValue) + info.AddValue("Weight", this.FontWeight.Value.ToOpenTypeWeight()); + info.AddValue("HasStyle", this.FontStyle.HasValue); + if (this.FontStyle.HasValue) + info.AddValue("Style", this.FontStyle.Value.ToString()); + info.AddValue("ExampleText", this.ExampleText); + } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return visitor.VisitColor(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdElement.cs new file mode 100644 index 000000000..d7634e63e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdElement.cs @@ -0,0 +1,29 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// An element in a XSHD rule set. + /// + [Serializable] + public abstract class XshdElement + { + /// + /// Gets the line number in the .xshd file. + /// + public int LineNumber { get; set; } + + /// + /// Gets the column number in the .xshd file. + /// + public int ColumnNumber { get; set; } + + /// + /// Applies the visitor to this element. + /// + public abstract object AcceptVisitor(IXshdVisitor visitor); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdImport.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdImport.cs new file mode 100644 index 000000000..68ecdd2d0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdImport.cs @@ -0,0 +1,25 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// <Import> element. + /// + [Serializable] + public class XshdImport : XshdElement + { + /// + /// Gets/sets the referenced rule set. + /// + public XshdReference RuleSetReference { get; set; } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return visitor.VisitImport(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdKeywords.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdKeywords.cs new file mode 100644 index 000000000..245f34ae5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdKeywords.cs @@ -0,0 +1,36 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A list of keywords. + /// + [Serializable] + public class XshdKeywords : XshdElement + { + /// + /// The color. + /// + public XshdReference ColorReference { get; set; } + + readonly NullSafeCollection words = new NullSafeCollection(); + + /// + /// Gets the list of key words. + /// + public IList Words { + get { return words; } + } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return visitor.VisitKeywords(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdProperty.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdProperty.cs new file mode 100644 index 000000000..3ca65f4b4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdProperty.cs @@ -0,0 +1,38 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A property in an Xshd file. + /// + [Serializable] + public class XshdProperty : XshdElement + { + /// + /// Gets/sets the name. + /// + public string Name { get; set; } + + /// + /// Gets/sets the value. + /// + public string Value { get; set; } + + /// + /// Creates a new XshdColor instance. + /// + public XshdProperty() + { + } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return null; +// return visitor.VisitProperty(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdReference.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdReference.cs new file mode 100644 index 000000000..363cd881c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdReference.cs @@ -0,0 +1,127 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A reference to an xshd color, or an inline xshd color. + /// + [Serializable] + public struct XshdReference : IEquatable> where T : XshdElement + { + string referencedDefinition; + string referencedElement; + T inlineElement; + + /// + /// Gets the reference. + /// + public string ReferencedDefinition { + get { return referencedDefinition; } + } + + /// + /// Gets the reference. + /// + public string ReferencedElement { + get { return referencedElement; } + } + + /// + /// Gets the inline element. + /// + public T InlineElement { + get { return inlineElement; } + } + + /// + /// Creates a new XshdReference instance. + /// + public XshdReference(string referencedDefinition, string referencedElement) + { + if (referencedElement == null) + throw new ArgumentNullException("referencedElement"); + this.referencedDefinition = referencedDefinition; + this.referencedElement = referencedElement; + this.inlineElement = null; + } + + /// + /// Creates a new XshdReference instance. + /// + public XshdReference(T inlineElement) + { + if (inlineElement == null) + throw new ArgumentNullException("inlineElement"); + this.referencedDefinition = null; + this.referencedElement = null; + this.inlineElement = inlineElement; + } + + /// + /// Applies the visitor to the inline element, if there is any. + /// + public object AcceptVisitor(IXshdVisitor visitor) + { + if (inlineElement != null) + return inlineElement.AcceptVisitor(visitor); + else + return null; + } + + #region Equals and GetHashCode implementation + // The code in this region is useful if you want to use this structure in collections. + // If you don't need it, you can just remove the region and the ": IEquatable" declaration. + + /// + public override bool Equals(object obj) + { + if (obj is XshdReference) + return Equals((XshdReference)obj); // use Equals method below + else + return false; + } + + /// + /// Equality operator. + /// + public bool Equals(XshdReference other) + { + // add comparisions for all members here + return this.referencedDefinition == other.referencedDefinition + && this.referencedElement == other.referencedElement + && this.inlineElement == other.inlineElement; + } + + /// + public override int GetHashCode() + { + // combine the hash codes of all members here (e.g. with XOR operator ^) + return GetHashCode(referencedDefinition) ^ GetHashCode(referencedElement) ^ GetHashCode(inlineElement); + } + + static int GetHashCode(object o) + { + return o != null ? o.GetHashCode() : 0; + } + + /// + /// Equality operator. + /// + public static bool operator ==(XshdReference left, XshdReference right) + { + return left.Equals(right); + } + + /// + /// Inequality operator. + /// + public static bool operator !=(XshdReference left, XshdReference right) + { + return !left.Equals(right); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRule.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRule.cs new file mode 100644 index 000000000..abca25f69 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRule.cs @@ -0,0 +1,35 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// <Rule> element. + /// + [Serializable] + public class XshdRule : XshdElement + { + /// + /// Gets/sets the rule regex. + /// + public string Regex { get; set; } + + /// + /// Gets/sets the rule regex type. + /// + public XshdRegexType RegexType { get; set; } + + /// + /// Gets/sets the color reference. + /// + public XshdReference ColorReference { get; set; } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return visitor.VisitRule(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRuleSet.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRuleSet.cs new file mode 100644 index 000000000..2ffa16143 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdRuleSet.cs @@ -0,0 +1,51 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A rule set in a XSHD file. + /// + [Serializable] + public class XshdRuleSet : XshdElement + { + /// + /// Gets/Sets the name of the rule set. + /// + public string Name { get; set; } + + /// + /// Gets/sets whether the case is ignored in expressions inside this rule set. + /// + public bool? IgnoreCase { get; set; } + + readonly NullSafeCollection elements = new NullSafeCollection(); + + /// + /// Gets the collection of elements. + /// + public IList Elements { + get { return elements; } + } + + /// + /// Applies the visitor to all elements. + /// + public void AcceptElements(IXshdVisitor visitor) + { + foreach (XshdElement element in Elements) { + element.AcceptVisitor(visitor); + } + } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return visitor.VisitRuleSet(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSpan.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSpan.cs new file mode 100644 index 000000000..6d32abad4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSpan.cs @@ -0,0 +1,82 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// Specifies the type of the regex. + /// + public enum XshdRegexType + { + /// + /// Normal regex. Used when the regex was specified as attribute. + /// + Default, + /// + /// Ignore pattern whitespace / allow regex comments. Used when the regex was specified as text element. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", + Justification = "Using the same case as the RegexOption")] + IgnorePatternWhitespace + } + + /// + /// <Span> element. + /// + [Serializable] + public class XshdSpan : XshdElement + { + /// + /// Gets/sets the begin regex. + /// + public string BeginRegex { get; set; } + + /// + /// Gets/sets the begin regex type. + /// + public XshdRegexType BeginRegexType { get; set; } + + /// + /// Gets/sets the end regex. + /// + public string EndRegex { get; set; } + + /// + /// Gets/sets the end regex type. + /// + public XshdRegexType EndRegexType { get; set; } + + /// + /// Gets/sets whether the span is multiline. + /// + public bool Multiline { get; set; } + + /// + /// Gets/sets the rule set reference. + /// + public XshdReference RuleSetReference { get; set; } + + /// + /// Gets/sets the span color. + /// + public XshdReference SpanColorReference { get; set; } + + /// + /// Gets/sets the span begin color. + /// + public XshdReference BeginColorReference { get; set; } + + /// + /// Gets/sets the span end color. + /// + public XshdReference EndColorReference { get; set; } + + /// + public override object AcceptVisitor(IXshdVisitor visitor) + { + return visitor.VisitSpan(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSyntaxDefinition.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSyntaxDefinition.cs new file mode 100644 index 000000000..347f14d25 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Xshd/XshdSyntaxDefinition.cs @@ -0,0 +1,50 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Highlighting.Xshd +{ + /// + /// A <SyntaxDefinition> element. + /// + [Serializable] + public class XshdSyntaxDefinition + { + /// + /// Creates a new XshdSyntaxDefinition object. + /// + public XshdSyntaxDefinition() + { + this.Elements = new NullSafeCollection(); + this.Extensions = new NullSafeCollection(); + } + + /// + /// Gets/sets the definition name + /// + public string Name { get; set; } + + /// + /// Gets the associated extensions. + /// + public IList Extensions { get; private set; } + + /// + /// Gets the collection of elements. + /// + public IList Elements { get; private set; } + + /// + /// Applies the visitor to all elements. + /// + public void AcceptElements(IXshdVisitor visitor) + { + foreach (XshdElement element in Elements) { + element.AcceptVisitor(visitor); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/class.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/class.png new file mode 100644 index 000000000..3e40469f5 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/class.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/enum.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/enum.png new file mode 100644 index 000000000..b79aa844f Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/enum.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/field.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/field.png new file mode 100644 index 000000000..763eadffb Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/field.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/interface.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/interface.png new file mode 100644 index 000000000..fb00f0cc7 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/interface.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/method.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/method.png new file mode 100644 index 000000000..249689941 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/method.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/namespace.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/namespace.png new file mode 100644 index 000000000..6520448ce Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/namespace.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/property.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/property.png new file mode 100644 index 000000000..d2f90f569 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/property.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/struct.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/struct.png new file mode 100644 index 000000000..02fbdbde7 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/struct.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationHelper.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationHelper.cs new file mode 100644 index 000000000..36aeb213b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationHelper.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Indentation.CSharp +{ + class CSharpIndentationHelper + { + public static string IndentCSharpCode(string code) + { + const string INDENT_STEP = "\t"; + + if (string.IsNullOrWhiteSpace(code)) + { + return code; + } + + var result = new StringBuilder(); + var indent = string.Empty; + var lineContent = false; + var stringDefinition = false; + + for (var i = 0; i < code.Length; i++) + { + var ch = code[i]; + + if (ch == '"' && !stringDefinition) + { + result.Append(ch); + stringDefinition = true; + continue; + } + + if (ch == '"' && stringDefinition) + { + result.Append(ch); + stringDefinition = false; + continue; + } + + if (stringDefinition) + { + result.Append(ch); + continue; + } + + if (ch == '{' && !stringDefinition) + { + if (lineContent) + { + result.AppendLine(); + } + + result.Append(indent).Append("{"); + + if (lineContent) + { + result.AppendLine(); + } + + indent += INDENT_STEP; + lineContent = false; + + continue; + } + + if (ch == '}' && !stringDefinition) + { + if (indent.Length != 0) + { + indent = indent.Substring(0, indent.Length - INDENT_STEP.Length); + } + + if (lineContent) + { + result.AppendLine(); + } + + result.Append(indent).Append("}"); + + if (lineContent) + { + result.AppendLine(); + } + + + lineContent = false; + + continue; + } + + if (ch == '\r') + { + continue; + } + + if ((ch == ' ' || ch == '\t') && !lineContent) + { + continue; + } + + if (ch == '\n') + { + lineContent = false; + result.AppendLine(); + + continue; + } + + if (!lineContent) + { + result.Append(indent); + lineContent = true; + } + + result.Append(ch); + } + + return result.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationStrategy.cs new file mode 100644 index 000000000..7b47099fe --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/CSharpIndentationStrategy.cs @@ -0,0 +1,96 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Indentation.CSharp +{ + /// + /// Smart indentation for C#. + /// + public class CSharpIndentationStrategy : DefaultIndentationStrategy + { + /// + /// Creates a new CSharpIndentationStrategy. + /// + public CSharpIndentationStrategy() + { + } + + /// + /// Creates a new CSharpIndentationStrategy and initializes the settings using the text editor options. + /// + public CSharpIndentationStrategy(TextEditorOptions options) + { + this.IndentationString = options.IndentationString; + } + + string indentationString = "\t"; + + /// + /// Gets/Sets the indentation string. + /// + public string IndentationString + { + get { return indentationString; } + set + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentException("Indentation string must not be null or empty"); + indentationString = value; + } + } + + /// + /// Performs indentation using the specified document accessor. + /// + /// Object used for accessing the document line-by-line + /// Specifies whether empty lines should be kept + public void Indent(IDocumentAccessor document, bool keepEmptyLines) + { + if (document == null) + throw new ArgumentNullException("document"); + IndentationSettings settings = new IndentationSettings(); + settings.IndentString = this.IndentationString; + settings.LeaveEmptyLines = keepEmptyLines; + + IndentationReformatter r = new IndentationReformatter(); + r.Reformat(document, settings); + } + + /// + public override void IndentLine(TextDocument document, DocumentLine line) + { + bool keepEmptyLines = true; + + if (line.PreviousLine != null && line.NextLine != null) + { + string previous_text = document.GetText(line.PreviousLine.Offset, line.PreviousLine.Length); + string next_text = document.GetText(line.NextLine.Offset, line.NextLine.Length); + + if (previous_text.Contains("{") && next_text.Contains("}") && previous_text.Length == next_text.Length) + { + keepEmptyLines = false; + } + } + + int lineNr = line.LineNumber; + TextDocumentAccessor acc = new TextDocumentAccessor(document, lineNr, lineNr); + Indent(acc, keepEmptyLines); + + string t = acc.Text; + if (t.Length == 0) + { + // use AutoIndentation for new lines in comments / verbatim strings. + base.IndentLine(document, line); + } + } + + /// + public override void IndentLines(TextDocument document, int beginLine, int endLine) + { + Indent(new TextDocumentAccessor(document, beginLine, endLine), true); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/DocumentAccessor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/DocumentAccessor.cs new file mode 100644 index 000000000..dbf6b21a9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/DocumentAccessor.cs @@ -0,0 +1,107 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.IO; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Indentation.CSharp +{ + /// + /// Interface used for the indentation class to access the document. + /// + public interface IDocumentAccessor + { + /// Gets if the current line is read only (because it is not in the + /// selected text region) + bool IsReadOnly { get; } + /// Gets the number of the current line. + int LineNumber { get; } + /// Gets/Sets the text of the current line. + string Text { get; set; } + /// Advances to the next line. + bool MoveNext(); + } + + #region TextDocumentAccessor + /// + /// Adapter IDocumentAccessor -> TextDocument + /// + public sealed class TextDocumentAccessor : IDocumentAccessor + { + readonly TextDocument doc; + readonly int minLine; + readonly int maxLine; + + /// + /// Creates a new TextDocumentAccessor. + /// + public TextDocumentAccessor(TextDocument document) + { + if (document == null) + throw new ArgumentNullException("document"); + doc = document; + this.minLine = 1; + this.maxLine = doc.LineCount; + } + + /// + /// Creates a new TextDocumentAccessor that indents only a part of the document. + /// + public TextDocumentAccessor(TextDocument document, int minLine, int maxLine) + { + if (document == null) + throw new ArgumentNullException("document"); + doc = document; + this.minLine = minLine; + this.maxLine = maxLine; + } + + int num; + string text; + DocumentLine line; + + /// + public bool IsReadOnly { + get { + return num < minLine; + } + } + + /// + public int LineNumber { + get { + return num; + } + } + + bool lineDirty; + + /// + public string Text { + get { return text; } + set { + if (num < minLine) return; + text = value; + lineDirty = true; + } + } + + /// + public bool MoveNext() + { + if (lineDirty) { + doc.Replace(line, text); + lineDirty = false; + } + ++num; + if (num > maxLine) return false; + line = doc.GetLineByNumber(num); + text = doc.GetText(line); + return true; + } + } + #endregion +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/IndentationReformatter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/IndentationReformatter.cs new file mode 100644 index 000000000..09e20c909 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/CSharp/IndentationReformatter.cs @@ -0,0 +1,474 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Tango.Scripting.Editors.Indentation.CSharp +{ + sealed class IndentationSettings + { + public string IndentString = "\t"; + /// Leave empty lines empty. + public bool LeaveEmptyLines = true; + } + + sealed class IndentationReformatter + { + /// + /// An indentation block. Tracks the state of the indentation. + /// + struct Block + { + /// + /// The indentation outside of the block. + /// + public string OuterIndent; + + /// + /// The indentation inside the block. + /// + public string InnerIndent; + + /// + /// The last word that was seen inside this block. + /// Because parenthesis open a sub-block and thus don't change their parent's LastWord, + /// this property can be used to identify the type of block statement (if, while, switch) + /// at the position of the '{'. + /// + public string LastWord; + + /// + /// The type of bracket that opened this block (, [ or { + /// + public char Bracket; + + /// + /// Gets whether there's currently a line continuation going on inside this block. + /// + public bool Continuation; + + /// + /// Gets whether there's currently a 'one-line-block' going on. 'one-line-blocks' occur + /// with if statements that don't use '{}'. They are not represented by a Block instance on + /// the stack, but are instead handled similar to line continuations. + /// This property is an integer because there might be multiple nested one-line-blocks. + /// As soon as there is a finished statement, OneLineBlock is reset to 0. + /// + public int OneLineBlock; + + /// + /// The previous value of one-line-block before it was reset. + /// Used to restore the indentation of 'else' to the correct level. + /// + public int PreviousOneLineBlock; + + public void ResetOneLineBlock() + { + PreviousOneLineBlock = OneLineBlock; + OneLineBlock = 0; + } + + /// + /// Gets the line number where this block started. + /// + public int StartLine; + + public void Indent(IndentationSettings set) + { + Indent(set.IndentString); + } + + public void Indent(string indentationString) + { + OuterIndent = InnerIndent; + InnerIndent += indentationString; + Continuation = false; + ResetOneLineBlock(); + LastWord = ""; + } + + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "[Block StartLine={0}, LastWord='{1}', Continuation={2}, OneLineBlock={3}, PreviousOneLineBlock={4}]", + this.StartLine, this.LastWord, this.Continuation, this.OneLineBlock, this.PreviousOneLineBlock); + } + } + + StringBuilder wordBuilder; + Stack blocks; // blocks contains all blocks outside of the current + Block block; // block is the current block + + bool inString; + bool inChar; + bool verbatim; + bool escape; + + bool lineComment; + bool blockComment; + + char lastRealChar; // last non-comment char + + public void Reformat(IDocumentAccessor doc, IndentationSettings set) + { + Init(); + + while (doc.MoveNext()) { + Step(doc, set); + } + } + + public void Init() + { + wordBuilder = new StringBuilder(); + blocks = new Stack(); + block = new Block(); + block.InnerIndent = ""; + block.OuterIndent = ""; + block.Bracket = '{'; + block.Continuation = false; + block.LastWord = ""; + block.OneLineBlock = 0; + block.PreviousOneLineBlock = 0; + block.StartLine = 0; + + inString = false; + inChar = false; + verbatim = false; + escape = false; + + lineComment = false; + blockComment = false; + + lastRealChar = ' '; // last non-comment char + } + + public void Step(IDocumentAccessor doc, IndentationSettings set) + { + string line = doc.Text; + if (set.LeaveEmptyLines && line.Length == 0) return; // leave empty lines empty + line = line.TrimStart(); + + StringBuilder indent = new StringBuilder(); + if (line.Length == 0) { + // Special treatment for empty lines: + if (blockComment || (inString && verbatim)) + return; + indent.Append(block.InnerIndent); + indent.Append(Repeat(set.IndentString, block.OneLineBlock)); + if (block.Continuation) + indent.Append(set.IndentString); + if (doc.Text != indent.ToString()) + doc.Text = indent.ToString(); + return; + } + + if (TrimEnd(doc)) + line = doc.Text.TrimStart(); + + Block oldBlock = block; + bool startInComment = blockComment; + bool startInString = (inString && verbatim); + + #region Parse char by char + lineComment = false; + inChar = false; + escape = false; + if (!verbatim) inString = false; + + lastRealChar = '\n'; + + char lastchar = ' '; + char c = ' '; + char nextchar = line[0]; + for (int i = 0; i < line.Length; i++) { + if (lineComment) break; // cancel parsing current line + + lastchar = c; + c = nextchar; + if (i + 1 < line.Length) + nextchar = line[i + 1]; + else + nextchar = '\n'; + + if (escape) { + escape = false; + continue; + } + + #region Check for comment/string chars + switch (c) { + case '/': + if (blockComment && lastchar == '*') + blockComment = false; + if (!inString && !inChar) { + if (!blockComment && nextchar == '/') + lineComment = true; + if (!lineComment && nextchar == '*') + blockComment = true; + } + break; + case '#': + if (!(inChar || blockComment || inString)) + lineComment = true; + break; + case '"': + if (!(inChar || lineComment || blockComment)) { + inString = !inString; + if (!inString && verbatim) { + if (nextchar == '"') { + escape = true; // skip escaped quote + inString = true; + } else { + verbatim = false; + } + } else if (inString && lastchar == '@') { + verbatim = true; + } + } + break; + case '\'': + if (!(inString || lineComment || blockComment)) { + inChar = !inChar; + } + break; + case '\\': + if ((inString && !verbatim) || inChar) + escape = true; // skip next character + break; + } + #endregion + + if (lineComment || blockComment || inString || inChar) { + if (wordBuilder.Length > 0) + block.LastWord = wordBuilder.ToString(); + wordBuilder.Length = 0; + continue; + } + + if (!Char.IsWhiteSpace(c) && c != '[' && c != '/') { + if (block.Bracket == '{') + block.Continuation = true; + } + + if (Char.IsLetterOrDigit(c)) { + wordBuilder.Append(c); + } else { + if (wordBuilder.Length > 0) + block.LastWord = wordBuilder.ToString(); + wordBuilder.Length = 0; + } + + #region Push/Pop the blocks + switch (c) { + case '{': + block.ResetOneLineBlock(); + blocks.Push(block); + block.StartLine = doc.LineNumber; + if (block.LastWord == "switch") { + block.Indent(set.IndentString + set.IndentString); + /* oldBlock refers to the previous line, not the previous block + * The block we want is not available anymore because it was never pushed. + * } else if (oldBlock.OneLineBlock) { + // Inside a one-line-block is another statement + // with a full block: indent the inner full block + // by one additional level + block.Indent(set, set.IndentString + set.IndentString); + block.OuterIndent += set.IndentString; + // Indent current line if it starts with the '{' character + if (i == 0) { + oldBlock.InnerIndent += set.IndentString; + }*/ + } else { + block.Indent(set); + } + block.Bracket = '{'; + break; + case '}': + while (block.Bracket != '{') { + if (blocks.Count == 0) break; + block = blocks.Pop(); + } + if (blocks.Count == 0) break; + block = blocks.Pop(); + block.Continuation = false; + block.ResetOneLineBlock(); + break; + case '(': + case '[': + blocks.Push(block); + if (block.StartLine == doc.LineNumber) + block.InnerIndent = block.OuterIndent; + else + block.StartLine = doc.LineNumber; + block.Indent(Repeat(set.IndentString, oldBlock.OneLineBlock) + + (oldBlock.Continuation ? set.IndentString : "") + + (i == line.Length - 1 ? set.IndentString : new String(' ', i + 1))); + block.Bracket = c; + break; + case ')': + if (blocks.Count == 0) break; + if (block.Bracket == '(') { + block = blocks.Pop(); + if (IsSingleStatementKeyword(block.LastWord)) + block.Continuation = false; + } + break; + case ']': + if (blocks.Count == 0) break; + if (block.Bracket == '[') + block = blocks.Pop(); + break; + case ';': + case ',': + block.Continuation = false; + block.ResetOneLineBlock(); + break; + case ':': + if (block.LastWord == "case" + || line.StartsWith("case ", StringComparison.Ordinal) + || line.StartsWith(block.LastWord + ":", StringComparison.Ordinal)) + { + block.Continuation = false; + block.ResetOneLineBlock(); + } + break; + } + + if (!Char.IsWhiteSpace(c)) { + // register this char as last char + lastRealChar = c; + } + #endregion + } + #endregion + + if (wordBuilder.Length > 0) + block.LastWord = wordBuilder.ToString(); + wordBuilder.Length = 0; + + if (startInString) return; + if (startInComment && line[0] != '*') return; + if (doc.Text.StartsWith("//\t", StringComparison.Ordinal) || doc.Text == "//") + return; + + if (line[0] == '}') { + indent.Append(oldBlock.OuterIndent); + oldBlock.ResetOneLineBlock(); + oldBlock.Continuation = false; + } else { + indent.Append(oldBlock.InnerIndent); + } + + if (indent.Length > 0 && oldBlock.Bracket == '(' && line[0] == ')') { + indent.Remove(indent.Length - 1, 1); + } else if (indent.Length > 0 && oldBlock.Bracket == '[' && line[0] == ']') { + indent.Remove(indent.Length - 1, 1); + } + + if (line[0] == ':') { + oldBlock.Continuation = true; + } else if (lastRealChar == ':' && indent.Length >= set.IndentString.Length) { + if (block.LastWord == "case" || line.StartsWith("case ", StringComparison.Ordinal) || line.StartsWith(block.LastWord + ":", StringComparison.Ordinal)) + indent.Remove(indent.Length - set.IndentString.Length, set.IndentString.Length); + } else if (lastRealChar == ')') { + if (IsSingleStatementKeyword(block.LastWord)) { + block.OneLineBlock++; + } + } else if (lastRealChar == 'e' && block.LastWord == "else") { + block.OneLineBlock = Math.Max(1, block.PreviousOneLineBlock); + block.Continuation = false; + oldBlock.OneLineBlock = block.OneLineBlock - 1; + } + + if (doc.IsReadOnly) { + // We can't change the current line, but we should accept the existing + // indentation if possible (=if the current statement is not a multiline + // statement). + if (!oldBlock.Continuation && oldBlock.OneLineBlock == 0 && + oldBlock.StartLine == block.StartLine && + block.StartLine < doc.LineNumber && lastRealChar != ':') + { + // use indent StringBuilder to get the indentation of the current line + indent.Length = 0; + line = doc.Text; // get untrimmed line + for (int i = 0; i < line.Length; ++i) { + if (!Char.IsWhiteSpace(line[i])) + break; + indent.Append(line[i]); + } + // /* */ multiline comments have an extra space - do not count it + // for the block's indentation. + if (startInComment && indent.Length > 0 && indent[indent.Length - 1] == ' ') { + indent.Length -= 1; + } + block.InnerIndent = indent.ToString(); + } + return; + } + + if (line[0] != '{') { + if (line[0] != ')' && oldBlock.Continuation && oldBlock.Bracket == '{') + indent.Append(set.IndentString); + indent.Append(Repeat(set.IndentString, oldBlock.OneLineBlock)); + } + + // this is only for blockcomment lines starting with *, + // all others keep their old indentation + if (startInComment) + indent.Append(' '); + + if (indent.Length != (doc.Text.Length - line.Length) || + !doc.Text.StartsWith(indent.ToString(), StringComparison.Ordinal) || + Char.IsWhiteSpace(doc.Text[indent.Length])) + { + doc.Text = indent.ToString() + line; + } + } + + static string Repeat(string text, int count) + { + if (count == 0) + return string.Empty; + if (count == 1) + return text; + StringBuilder b = new StringBuilder(text.Length * count); + for (int i = 0; i < count; i++) + b.Append(text); + return b.ToString(); + } + + static bool IsSingleStatementKeyword(string keyword) + { + switch (keyword) { + case "if": + case "for": + case "while": + case "do": + case "foreach": + case "using": + case "lock": + return true; + default: + return false; + } + } + + static bool TrimEnd(IDocumentAccessor doc) + { + string line = doc.Text; + if (!Char.IsWhiteSpace(line[line.Length - 1])) return false; + + // one space after an empty comment is allowed + if (line.EndsWith("// ", StringComparison.Ordinal) || line.EndsWith("* ", StringComparison.Ordinal)) + return false; + + doc.Text = line.TrimEnd(); + return true; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/DefaultIndentationStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/DefaultIndentationStrategy.cs new file mode 100644 index 000000000..b5a1f0c2c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/DefaultIndentationStrategy.cs @@ -0,0 +1,40 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Indentation +{ + /// + /// Handles indentation by copying the indentation from the previous line. + /// Does not support indenting multiple lines. + /// + public class DefaultIndentationStrategy : IIndentationStrategy + { + /// + public virtual void IndentLine(TextDocument document, DocumentLine line) + { + if (document == null) + throw new ArgumentNullException("document"); + if (line == null) + throw new ArgumentNullException("line"); + DocumentLine previousLine = line.PreviousLine; + if (previousLine != null) { + ISegment indentationSegment = TextUtilities.GetWhitespaceAfter(document, previousLine.Offset); + string indentation = document.GetText(indentationSegment); + // copy indentation to line + indentationSegment = TextUtilities.GetWhitespaceAfter(document, line.Offset); + document.Replace(indentationSegment, indentation); + } + } + + /// + /// Does nothing: indenting multiple lines is useless without a smart indentation strategy. + /// + public virtual void IndentLines(TextDocument document, int beginLine, int endLine) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/IIndentationStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/IIndentationStrategy.cs new file mode 100644 index 000000000..0697fb642 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Indentation/IIndentationStrategy.cs @@ -0,0 +1,25 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Document; +using System; + +namespace Tango.Scripting.Editors.Indentation +{ + /// + /// Strategy how the text editor handles indentation when new lines are inserted. + /// + public interface IIndentationStrategy + { + /// + /// Sets the indentation for the specified line. + /// Usually this is constructed from the indentation of the previous line. + /// + void IndentLine(TextDocument document, DocumentLine line); + + /// + /// Reindents a set of lines. + /// + void IndentLines(TextDocument document, int beginLine, int endLine); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItem.cs new file mode 100644 index 000000000..a1734ba30 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItem.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class ClassCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("class.png"); + + public override BitmapSource Image => image; + public override string Text => Name; + + public String Name { get; set; } + public String Namespace { get; set; } + public override CompletionItemPopupControl PopupControl => new ClassCompletionItemPopup(); + + public override void Complete(ScriptEditor editor) + { + base.Complete(editor); + + if (Text.Contains("")) + { + editor.CaretOffset -= 2; + editor.Select(editor.CaretOffset, 1); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItemPopup.cs new file mode 100644 index 000000000..d575ba9db --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ClassCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class ClassCompletionItemPopup : CompletionItemPopupControl + { + static ClassCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ClassCompletionItemPopup), new FrameworkPropertyMetadata(typeof(ClassCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItem.cs new file mode 100644 index 000000000..c8beebd28 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItem.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; + +namespace Tango.Scripting.Editors.Intellisense +{ + public abstract class CompletionItem : DependencyObject, ICompletionItem + { + public abstract string Text { get; } + public object Description { get; set; } + public double Priority { get; set; } + public abstract CompletionItemPopupControl PopupControl { get; } + + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } + public static readonly DependencyProperty IsSelectedProperty = + DependencyProperty.Register("IsSelected", typeof(bool), typeof(CompletionItem), new PropertyMetadata(false)); + + public virtual void Complete(ScriptEditor editor) + { + var word = editor.GetCurrentWord(); + int index = editor.GetCurrentWordStartIndex(); + int max = editor.GetCurrentLine().EndOffset; + + editor.Document.Replace(index, word.Length,Text); + } + + public abstract BitmapSource Image { get; } + + protected static BitmapSource GetImage(String name) + { + return new BitmapImage(new Uri($"pack://application:,,,/Tango.Scripting.Editors;component/Images/{name}", UriKind.Absolute)); + } + + public override string ToString() + { + return Text; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItemPopupControl.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItemPopupControl.cs new file mode 100644 index 000000000..14e5b6681 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/CompletionItemPopupControl.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace Tango.Scripting.Editors.Intellisense +{ + public abstract class CompletionItemPopupControl : Control + { + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItem.cs new file mode 100644 index 000000000..c8f34347e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItem.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class EnumCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("enum.png"); + + public override BitmapSource Image => image; + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new EnumCompletionItemPopup(); + + public String Name { get; set; } + public String Namespace { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItemPopup.cs new file mode 100644 index 000000000..0ef29c338 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EnumCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class EnumCompletionItemPopup : CompletionItemPopupControl + { + static EnumCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumCompletionItemPopup), new FrameworkPropertyMetadata(typeof(EnumCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItem.cs new file mode 100644 index 000000000..3b1996174 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItem.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class FieldCompletionItem : CompletionItem + { + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new FieldCompletionItemPopup(); + public override BitmapSource Image => GetImage("field.png"); + + public String Name { get; set; } + public String Class { get; set; } + public String Type { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItemPopup.cs new file mode 100644 index 000000000..e1ea3ce55 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/FieldCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class FieldCompletionItemPopup : CompletionItemPopupControl + { + static FieldCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(FieldCompletionItemPopup), new FrameworkPropertyMetadata(typeof(FieldCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionItem.cs new file mode 100644 index 000000000..83e304e8b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionItem.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Scripting.Editors.CodeCompletion; + +namespace Tango.Scripting.Editors.Intellisense +{ + public interface ICompletionItem : ICompletionData + { + CompletionItemPopupControl PopupControl { get; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionProvider.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionProvider.cs new file mode 100644 index 000000000..bc48ce401 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/ICompletionProvider.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public interface ICompletionProvider + { + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItem.cs new file mode 100644 index 000000000..56f6db7af --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItem.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class InterfaceCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("interface.png"); + + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new InterfaceCompletionItemPopup(); + public override BitmapSource Image => image; + + public String Name { get; set; } + public String Namespace { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItemPopup.cs new file mode 100644 index 000000000..126a81c4c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/InterfaceCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class InterfaceCompletionItemPopup : CompletionItemPopupControl + { + static InterfaceCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(InterfaceCompletionItemPopup), new FrameworkPropertyMetadata(typeof(InterfaceCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs new file mode 100644 index 000000000..28f9ccb9a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownType.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Tango.Core; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class KnownType + { + private bool _initialized; + private bool _documentationLoaded; + + public Type Type { get; private set; } + public String Name { get; private set; } + public String TypeDefinition { get; private set; } + public String FriendlyName { get; private set; } + public String Summary { get; set; } + public List Constructors { get; set; } + public List Methods { get; set; } + public List Properties { get; set; } + public List Members + { + get + { + List members = new List(); + + members.AddRange(Properties); + members.AddRange(Methods); + + return members.OrderBy(x => x.Name).ToList(); + } + } + public List Fields { get; set; } + + public KnownType(Type type) + { + Summary = "Loading documentation..."; + Constructors = new List(); + Methods = new List(); + Properties = new List(); + Fields = new List(); + Type = type; + Name = type.Name; + + Init(); + } + + public override string ToString() + { + return Type.ToString(); + } + + private void Init() + { + InitTypeDefinition(); + InitFriendlyName(); + InitType(); + } + + private void InitFriendlyName() + { + FriendlyName = Type.GetFriendlyName(); + } + + private void InitTypeDefinition() + { + if (Type.IsClass) TypeDefinition = "class"; + else if (Type.IsEnum) TypeDefinition = "enum"; + else if (Type.IsInterface) TypeDefinition = "interface"; + else if (Type.IsValueType) TypeDefinition = "struct"; + } + + private void InitType() + { + if (!_initialized) + { + _initialized = true; + + //Load Constructors... + { + var constructors = Type.GetConstructors().Where(x => x.IsPublic).ToList(); + + for (int i = 0; i < constructors.Count; i++) + { + var constructor = constructors[i]; + + KnownTypeConstructor c = new KnownTypeConstructor(this); + + var parameters = constructor.GetParameters().ToList(); + + for (int j = 0; j < parameters.Count; j++) + { + var parameter = parameters[j]; + + KnownTypeMethodParameter p = new KnownTypeMethodParameter(); + p.Type = parameter.ParameterType.GetFriendlyName(); + p.Name = parameter.Name; + + if (j == parameters.Count - 1) + { + p.IsLast = true; + } + + c.Parameters.Add(p); + } + + Constructors.Add(c); + } + } + + //Load Methods... + { + var methods = Type.GetRuntimeMethods().Where(x => x.IsPublic && !x.IsSpecialName).ToList(); + + //TODO: Separate extension methods! + methods.AddRange(Type.GetExtensionMethods(Type.Assembly).ToList()); + + if (typeof(IEnumerable).IsAssignableFrom(Type)) + { + var linqMethods = typeof(System.Linq.Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).ToList(); + methods.AddRange(linqMethods); + } + + for (int i = 0; i < methods.Count; i++) + { + var method = methods[i]; + + KnownTypeMethod m = new KnownTypeMethod(this); + m.Name = method.Name; + m.ReturnType = method.ReturnType; + m.ReturnTypeFriendlyName = method.ReturnType.GetFriendlyName(); + + if (method.IsGenericMethod) + { + foreach (var lGenericArgument in method.GetGenericMethodDefinition().GetGenericArguments()) + { + m.TypeArguments.Add(lGenericArgument.Name); + } + } + + var parameters = method.GetParameters().ToList(); + + bool isLinq = method.DeclaringType == typeof(Enumerable); + + for (int j = 0; j < parameters.Count; j++) + { + var parameter = parameters[j]; + + KnownTypeMethodParameter p = new KnownTypeMethodParameter(); + p.Type = parameter.ParameterType.GetFriendlyName(); + p.Name = parameter.Name; + + if (j == parameters.Count - 1) + { + p.IsLast = true; + } + + if (j == 0 && isLinq) continue; + m.Parameters.Add(p); + } + + Methods.Add(m); + } + } + + //Load Properties + { + var properties = Type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.PropertyType.IsPublic).ToList(); + + for (int i = 0; i < properties.Count; i++) + { + var property = properties[i]; + + KnownTypeProperty p = new KnownTypeProperty(this); + p.Name = property.Name; + p.ReturnType = property.PropertyType; + p.ReturnTypeFriendlyName = property.PropertyType.GetFriendlyName(); + + Properties.Add(p); + } + } + + //Load Enum Values + { + if (Type.IsEnum) + { + var values = Enum.GetNames(Type).ToList(); + + for (int i = 0; i < values.Count; i++) + { + var value = values[i]; + + KnownTypeField f = new KnownTypeField(this); + f.Name = value; + f.ReturnType = typeof(Int32); + f.ReturnTypeFriendlyName = typeof(Int32).Name; + Fields.Add(f); + } + } + } + + _initialized = true; + } + } + + public void LoadDocumentation() + { + if (!_documentationLoaded) + { + _documentationLoaded = true; + + Utils.LoadKnownTypeDocs(this); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeConstructor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeConstructor.cs new file mode 100644 index 000000000..83dc3f750 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeConstructor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class KnownTypeConstructor + { + public KnownType Type { get; set; } + + public String Summary { get; set; } + + public List Parameters { get; set; } + + public KnownTypeConstructor() + { + Summary = "Loading documentation..."; + Parameters = new List(); + } + + public KnownTypeConstructor(KnownType knownType) : this() + { + Type = knownType; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeField.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeField.cs new file mode 100644 index 000000000..cd1349744 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeField.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class KnownTypeField : KnownTypeMember + { + public KnownTypeField() + { + Summary = "Loading documentation..."; + } + + public KnownTypeField(KnownType knownType) : this() + { + Type = knownType; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMember.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMember.cs new file mode 100644 index 000000000..bae4edf41 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMember.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public abstract class KnownTypeMember + { + public Type ReturnType { get; set; } + + public String ReturnTypeFriendlyName { get; set; } + + public KnownType Type { get; set; } + + public String Summary { get; set; } + + public String Name { get; set; } + + public KnownTypeMember() + { + Summary = "Loading documentation..."; + } + + public KnownTypeMember(KnownType knownType) : this() + { + Type = knownType; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethod.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethod.cs new file mode 100644 index 000000000..f84e26fe5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethod.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class KnownTypeMethod : KnownTypeMember + { + public List Parameters { get; set; } + + public List TypeArguments { get; set; } + + public String NameWithTypeArguments + { + get + { + if (TypeArguments.Count == 0) + { + return Name; + } + else + { + return Name + $"<{String.Join(",", TypeArguments)}>"; + } + } + } + + public KnownTypeMethod() + { + Summary = "Loading documentation..."; + Parameters = new List(); + TypeArguments = new List(); + } + + public KnownTypeMethod(KnownType knownType) : this() + { + Type = knownType; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethodParameter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethodParameter.cs new file mode 100644 index 000000000..59f6db4fd --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeMethodParameter.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class KnownTypeMethodParameter + { + public String Type { get; set; } + public String Name { get; set; } + public String Description { get; set; } + public bool IsLast { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeProperty.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeProperty.cs new file mode 100644 index 000000000..52717579a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeProperty.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class KnownTypeProperty : KnownTypeMember + { + public KnownTypeProperty() + { + Summary = "Loading documentation..."; + } + + public KnownTypeProperty(KnownType knownType) : this() + { + Type = knownType; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItem.cs new file mode 100644 index 000000000..d2ee40920 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItem.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class MethodCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("method.png"); + + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new MethodCompletionItemPopup(); + public override BitmapSource Image => image; + + public String Class { get; set; } + public String Name { get; set; } + public String ReturnType { get; set; } + + public int Overloads { get; set; } + + public bool HasOverloads + { + get { return Overloads > 0; } + } + + public List Parameters { get; set; } + + public override void Complete(ScriptEditor editor) + { + base.Complete(editor); + + if (Text.Contains("")) + { + editor.CaretOffset -= 2; + editor.Select(editor.CaretOffset, 1); + } + } + + public MethodCompletionItem() + { + Parameters = new List(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItemPopup.cs new file mode 100644 index 000000000..1b9717307 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/MethodCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class MethodCompletionItemPopup : CompletionItemPopupControl + { + static MethodCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodCompletionItemPopup), new FrameworkPropertyMetadata(typeof(MethodCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItem.cs new file mode 100644 index 000000000..d5c8db38a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItem.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class NamespaceCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("namespace.png"); + + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new NamespaceCompletionItemPopup(); + public override BitmapSource Image => image; + + public String Name { get; set; } + public String Assembly { get; set; } + + public override void Complete(ScriptEditor editor) + { + var line = editor.GetCurrentLine(); + editor.Document.Replace(line, "using " + Name); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItemPopup.cs new file mode 100644 index 000000000..7adc82fd8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/NamespaceCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class NamespaceCompletionItemPopup : CompletionItemPopupControl + { + static NamespaceCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(NamespaceCompletionItemPopup), new FrameworkPropertyMetadata(typeof(NamespaceCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItem.cs new file mode 100644 index 000000000..c301e8ede --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItem.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class PropertyCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("property.png"); + + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new PropertyCompletionItemPopup(); + public override BitmapSource Image => image; + + public String Name { get; set; } + public String Class { get; set; } + public String Type { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItemPopup.cs new file mode 100644 index 000000000..7790d6c43 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/PropertyCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class PropertyCompletionItemPopup : CompletionItemPopupControl + { + static PropertyCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyCompletionItemPopup), new FrameworkPropertyMetadata(typeof(PropertyCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItem.cs new file mode 100644 index 000000000..b89a7cee2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItem.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class StructCompletionItem : CompletionItem + { + private static BitmapSource image = GetImage("struct.png"); + + public override string Text => Name; + public override CompletionItemPopupControl PopupControl => new StructCompletionItemPopup(); + public override BitmapSource Image => image; + + public String Name { get; set; } + public String Namespace { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItemPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItemPopup.cs new file mode 100644 index 000000000..09512f405 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/StructCompletionItemPopup.cs @@ -0,0 +1,25 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Intellisense +{ + public class StructCompletionItemPopup : CompletionItemPopupControl + { + static StructCompletionItemPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(StructCompletionItemPopup), new FrameworkPropertyMetadata(typeof(StructCompletionItemPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/Utils.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/Utils.cs new file mode 100644 index 000000000..f8cc7072d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/Utils.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Tango.Scripting.Editors.Intellisense +{ + public static class Utils + { + private static String dotNetXmlFolder = @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.X"; + private static Dictionary _assemblies_docs_cache; + + static Utils() + { + _assemblies_docs_cache = new Dictionary(); + } + + public static void LoadKnownTypeDocs(KnownType knownType) + { + XmlDocument xmlDoc = null; + + if (_assemblies_docs_cache.ContainsKey(knownType.Type.Assembly)) + { + xmlDoc = _assemblies_docs_cache[knownType.Type.Assembly]; + } + + if (xmlDoc == null) + { + String dllPath = knownType.Type.Assembly.Location; + + string docuPath = dllPath.Substring(0, dllPath.LastIndexOf(".")) + ".XML"; + + if (File.Exists(docuPath)) + { + xmlDoc = new XmlDocument(); + xmlDoc.Load(docuPath); + } + else if (File.Exists(System.IO.Path.Combine(dotNetXmlFolder, System.IO.Path.GetFileName(docuPath)))) + { + xmlDoc = new XmlDocument(); + xmlDoc.Load(System.IO.Path.Combine(dotNetXmlFolder, System.IO.Path.GetFileName(docuPath))); + } + + if (xmlDoc != null) + { + _assemblies_docs_cache.Add(knownType.Type.Assembly, xmlDoc); + } + } + + if (xmlDoc == null) + { + xmlDoc = new XmlDocument(); + } + + //Load Type Summary + { + string path = "T:" + knownType.Type.FullName; + XmlNode xmlDocuOfType = xmlDoc.SelectSingleNode("//member[starts-with(@name, '" + path + "')]"); + + if (xmlDocuOfType != null) + { + XmlNode summaryNode = xmlDocuOfType.SelectSingleNode("summary"); + knownType.Summary = summaryNode.InnerText; + } + } + + //Load Constructors... + { + string path = "M:" + knownType.Type.FullName + ".#ctor"; + + var docNodes = xmlDoc.SelectNodes("//member[starts-with(@name, '" + path + "')]").OfType().ToList(); + + for (int i = 0; i < knownType.Constructors.Count; i++) + { + var constructor = knownType.Constructors[i]; + XmlNode cDoc = null; + + if (i < docNodes.Count) + { + cDoc = docNodes[i]; + } + + constructor.Summary = cDoc != null ? cDoc.SelectSingleNode("summary").InnerXml : $"Initializes a new instance of {knownType.FriendlyName}."; + + var parameters = constructor.Parameters.ToList(); + var parametersNodes = cDoc != null ? cDoc.SelectNodes("param").OfType().ToList() : new List(); + + for (int j = 0; j < parameters.Count; j++) + { + var parameter = parameters[j]; + XmlNode pNode = null; + + if (j < parametersNodes.Count) + { + pNode = parametersNodes[j]; + } + + parameter.Description = pNode != null ? pNode.InnerText : null; + } + } + } + + //Load Methods... + { + string path = "M:" + knownType.Type.FullName; + + var docNodes = xmlDoc.SelectNodes("//member[starts-with(@name, '" + path + "')]").OfType().ToList(); + + for (int i = 0; i < knownType.Methods.Count; i++) + { + var method = knownType.Methods[i]; + XmlNode mDoc = null; + + mDoc = docNodes.FirstOrDefault(x => x.Attributes["name"].InnerText.Contains(knownType.Type.Name + "." + method.Name)); + method.Summary = mDoc != null ? mDoc.SelectSingleNode("summary").InnerXml.Remove("()") : "No documentation"; + + var parameters = method.Parameters.ToList(); + var parametersNodes = mDoc != null ? mDoc.SelectNodes("param").OfType().ToList() : new List(); + + bool isLinq = knownType.Type == typeof(Enumerable); + + for (int j = 0; j < parameters.Count; j++) + { + var parameter = parameters[j]; + + XmlNode pNode = null; + + if (j < parametersNodes.Count) + { + pNode = parametersNodes[j]; + } + + parameter.Description = pNode != null ? pNode.InnerText.Remove("()") : null; + } + } + } + + //Load Properties + { + string path = "P:" + knownType.Type.FullName; + + var docNodes = xmlDoc.SelectNodes("//member[starts-with(@name, '" + path + "')]").OfType().ToList(); + + for (int i = 0; i < knownType.Properties.Count; i++) + { + var property = knownType.Properties[i]; + var pDoc = docNodes.FirstOrDefault(x => x.Attributes["name"].InnerText.Contains(knownType.Type.Name + "." + property.Name)); + + property.Summary = pDoc != null ? pDoc.SelectSingleNode("summary").InnerXml : "No documentation"; + } + } + + //Load Enum Values + { + if (knownType.Type.IsEnum) + { + for (int i = 0; i < knownType.Fields.Count; i++) + { + var field = knownType.Fields[i]; + var pDoc = xmlDoc.SelectSingleNode("//member[starts-with(@name, '" + $"F:{knownType.Type.FullName}.{field.Name}" + "')]"); + field.Summary = pDoc != null ? pDoc.SelectSingleNode("summary").InnerXml : "No documentation"; + } + } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodDescription.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodDescription.cs new file mode 100644 index 000000000..70e0d028d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodDescription.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Popups +{ + public class MethodDescription + { + public String Description { get; set; } + public String ReturnType { get; set; } + public List Parameters { get; set; } + public String Class { get; set; } + public String Name { get; set; } + + public MethodDescription() + { + Parameters = new List(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodPopup.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodPopup.cs new file mode 100644 index 000000000..7c431f9b4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/MethodPopup.cs @@ -0,0 +1,82 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.Editors.Popups +{ + public class MethodPopup : Control + { + public List Methods { get; set; } + + public int CurrentMethodIndex + { + get { return (int)GetValue(CurrentMethodIndexProperty); } + set { SetValue(CurrentMethodIndexProperty, value); } + } + public static readonly DependencyProperty CurrentMethodIndexProperty = + DependencyProperty.Register("CurrentMethodIndex", typeof(int), typeof(MethodPopup), new PropertyMetadata(1)); + + public MethodDescription CurrentMethod + { + get { return (MethodDescription)GetValue(CurrentMethodProperty); } + set { SetValue(CurrentMethodProperty, value); } + } + public static readonly DependencyProperty CurrentMethodProperty = + DependencyProperty.Register("CurrentMethod", typeof(MethodDescription), typeof(MethodPopup), new PropertyMetadata(null)); + + public void IncrementMethod() + { + if (Methods.Count > 0) + { + if (CurrentMethodIndex < Methods.Count) + { + CurrentMethodIndex++; + CurrentMethod = Methods[CurrentMethodIndex - 1]; + } + else + { + CurrentMethodIndex = 1; + CurrentMethod = Methods[0]; + } + } + } + + public void DecrementMethod() + { + if (Methods.Count > 0) + { + if (CurrentMethodIndex > 1) + { + CurrentMethodIndex--; + CurrentMethod = Methods[CurrentMethodIndex - 1]; + } + else + { + CurrentMethodIndex = Methods.Count; + CurrentMethod = Methods[Methods.Count - 1]; + } + } + } + + public MethodPopup() + { + Methods = new List(); + } + + static MethodPopup() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodPopup), new FrameworkPropertyMetadata(typeof(MethodPopup))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/ParameterDescription.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/ParameterDescription.cs new file mode 100644 index 000000000..6650f76ec --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Popups/ParameterDescription.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Editors.Popups +{ + public class ParameterDescription + { + public ParameterDescription(MethodDescription method) + { + Method = method; + } + + public MethodDescription Method { get; set; } + public String Type { get; set; } + public String Name { get; set; } + public String Description { get; set; } + + public bool IsLast + { + get { return Method.Parameters.Last() == this; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..119bc64c0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +#region Using directives + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Markup; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", + Justification = "AssemblyInformationalVersion does not need to be a parsable version")] +[assembly: AssemblyTitle("Tango.Scripting.Editors")] +[assembly: AssemblyDescription("WPF-based extensible text editor")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +//[assembly: CLSCompliant(true)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/CodeAnalysisDictionary.xml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/CodeAnalysisDictionary.xml new file mode 100644 index 000000000..71301b91d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Properties/CodeAnalysisDictionary.xml @@ -0,0 +1,33 @@ + + + + + + Uncollapse + Foldings + Xshd + Utils + Deque + Colorizer + Renderer + Renderers + Deletable + + y + + + + Lineup + + + + Bestest + + + + + + WiX + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/BackgroundGeometryBuilder.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/BackgroundGeometryBuilder.cs new file mode 100644 index 000000000..d3c839b35 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/BackgroundGeometryBuilder.cs @@ -0,0 +1,343 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Helper for creating a PathGeometry. + /// + public sealed class BackgroundGeometryBuilder + { + double cornerRadius; + + /// + /// Gets/sets the radius of the rounded corners. + /// + public double CornerRadius { + get { return cornerRadius; } + set { cornerRadius = value; } + } + + /// + /// Gets/Sets whether to align the geometry to whole pixels. + /// + public bool AlignToWholePixels { get; set; } + + /// + /// Gets/Sets whether to align the geometry to the middle of pixels. + /// + public bool AlignToMiddleOfPixels { get; set; } + + /// + /// Gets/Sets whether to extend the rectangles to full width at line end. + /// + public bool ExtendToFullWidthAtLineEnd { get; set; } + + /// + /// Creates a new BackgroundGeometryBuilder instance. + /// + public BackgroundGeometryBuilder() + { + } + + /// + /// Adds the specified segment to the geometry. + /// + public void AddSegment(TextView textView, ISegment segment) + { + if (textView == null) + throw new ArgumentNullException("textView"); + Size pixelSize = PixelSnapHelpers.GetPixelSize(textView); + foreach (Rect r in GetRectsForSegment(textView, segment, ExtendToFullWidthAtLineEnd)) { + AddRectangle(pixelSize, r); + } + } + + /// + /// Adds a rectangle to the geometry. + /// + /// + /// This overload will align the coordinates according to + /// or . + /// Use the -overload instead if the coordinates should not be aligned. + /// + public void AddRectangle(TextView textView, Rect rectangle) + { + AddRectangle(PixelSnapHelpers.GetPixelSize(textView), rectangle); + } + + void AddRectangle(Size pixelSize, Rect r) + { + if (AlignToWholePixels) { + AddRectangle(PixelSnapHelpers.Round(r.Left, pixelSize.Width), + PixelSnapHelpers.Round(r.Top + 1, pixelSize.Height), + PixelSnapHelpers.Round(r.Right, pixelSize.Width), + PixelSnapHelpers.Round(r.Bottom + 1, pixelSize.Height)); + } else if (AlignToMiddleOfPixels) { + AddRectangle(PixelSnapHelpers.PixelAlign(r.Left, pixelSize.Width), + PixelSnapHelpers.PixelAlign(r.Top + 1, pixelSize.Height), + PixelSnapHelpers.PixelAlign(r.Right, pixelSize.Width), + PixelSnapHelpers.PixelAlign(r.Bottom + 1, pixelSize.Height)); + } else { + AddRectangle(r.Left, r.Top + 1, r.Right, r.Bottom + 1); + } + } + + /// + /// Calculates the list of rectangle where the segment in shown. + /// This method usually returns one rectangle for each line inside the segment + /// (but potentially more, e.g. when bidirectional text is involved). + /// + public static IEnumerable GetRectsForSegment(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd = false) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (segment == null) + throw new ArgumentNullException("segment"); + return GetRectsForSegmentImpl(textView, segment, extendToFullWidthAtLineEnd); + } + + static IEnumerable GetRectsForSegmentImpl(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd) + { + int segmentStart = segment.Offset; + int segmentEnd = segment.Offset + segment.Length; + + segmentStart = segmentStart.CoerceValue(0, textView.Document.TextLength); + segmentEnd = segmentEnd.CoerceValue(0, textView.Document.TextLength); + + TextViewPosition start; + TextViewPosition end; + + if (segment is SelectionSegment) { + SelectionSegment sel = (SelectionSegment)segment; + start = new TextViewPosition(textView.Document.GetLocation(sel.StartOffset), sel.StartVisualColumn); + end = new TextViewPosition(textView.Document.GetLocation(sel.EndOffset), sel.EndVisualColumn); + } else { + start = new TextViewPosition(textView.Document.GetLocation(segmentStart), -1); + end = new TextViewPosition(textView.Document.GetLocation(segmentEnd), -1); + } + + foreach (VisualLine vl in textView.VisualLines) { + int vlStartOffset = vl.FirstDocumentLine.Offset; + if (vlStartOffset > segmentEnd) + break; + int vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length; + if (vlEndOffset < segmentStart) + continue; + + int segmentStartVC; + if (segmentStart < vlStartOffset) + segmentStartVC = 0; + else + segmentStartVC = vl.ValidateVisualColumn(start, extendToFullWidthAtLineEnd); + + int segmentEndVC; + if (segmentEnd > vlEndOffset) + segmentEndVC = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker; + else + segmentEndVC = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd); + + foreach (var rect in ProcessTextLines(textView, vl, segmentStartVC, segmentEndVC)) + yield return rect; + } + } + + /// + /// Calculates the rectangles for the visual column segment. + /// This returns one rectangle for each line inside the segment. + /// + public static IEnumerable GetRectsFromVisualSegment(TextView textView, VisualLine line, int startVC, int endVC) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (line == null) + throw new ArgumentNullException("line"); + return ProcessTextLines(textView, line, startVC, endVC); + } + + static IEnumerable ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVC, int segmentEndVC) + { + TextLine lastTextLine = visualLine.TextLines.Last(); + Vector scrollOffset = textView.ScrollOffset; + + for (int i = 0; i < visualLine.TextLines.Count; i++) { + TextLine line = visualLine.TextLines[i]; + double y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop); + int visualStartCol = visualLine.GetTextLineVisualStartColumn(line); + int visualEndCol = visualStartCol + line.Length; + if (line != lastTextLine) + visualEndCol -= line.TrailingWhitespaceLength; + + if (segmentEndVC < visualStartCol) + break; + if (lastTextLine != line && segmentStartVC > visualEndCol) + continue; + int segmentStartVCInLine = Math.Max(segmentStartVC, visualStartCol); + int segmentEndVCInLine = Math.Min(segmentEndVC, visualEndCol); + y -= scrollOffset.Y; + if (segmentStartVCInLine == segmentEndVCInLine) { + // GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit + // We need to return a rectangle to ensure empty lines are still visible + double pos = visualLine.GetTextLineVisualXPosition(line, segmentStartVCInLine); + pos -= scrollOffset.X; + // The following special cases are necessary to get rid of empty rectangles at the end of a TextLine if "Show Spaces" is active. + // If not excluded once, the same rectangle is calculated (and added) twice (since the offset could be mapped to two visual positions; end/start of line), if there is no trailing whitespace. + // Skip this TextLine segment, if it is at the end of this line and this line is not the last line of the VisualLine and the selection continues and there is no trailing whitespace. + if (segmentEndVCInLine == visualEndCol && i < visualLine.TextLines.Count - 1 && segmentEndVC > segmentEndVCInLine && line.TrailingWhitespaceLength == 0) + continue; + if (segmentStartVCInLine == visualStartCol && i > 0 && segmentStartVC < segmentStartVCInLine && visualLine.TextLines[i - 1].TrailingWhitespaceLength == 0) + continue; + yield return new Rect(pos, y, 1, line.Height); + } else { + Rect lastRect = Rect.Empty; + if (segmentStartVCInLine <= visualEndCol) { + foreach (TextBounds b in line.GetTextBounds(segmentStartVCInLine, segmentEndVCInLine - segmentStartVCInLine)) { + double left = b.Rectangle.Left - scrollOffset.X; + double right = b.Rectangle.Right - scrollOffset.X; + if (!lastRect.IsEmpty) + yield return lastRect; + // left>right is possible in RTL languages + lastRect = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); + } + } + if (segmentEndVC >= visualLine.VisualLengthWithEndOfLineMarker) { + double left = (segmentStartVC > visualLine.VisualLengthWithEndOfLineMarker ? visualLine.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X; + double right = ((segmentEndVC == int.MaxValue || line != lastTextLine) ? Math.Max(((IScrollInfo)textView).ExtentWidth, ((IScrollInfo)textView).ViewportWidth) : visualLine.GetTextLineVisualXPosition(lastTextLine, segmentEndVC)) - scrollOffset.X; + Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); + if (!lastRect.IsEmpty) { + if (extendSelection.IntersectsWith(lastRect)) { + lastRect.Union(extendSelection); + yield return lastRect; + } else { + yield return lastRect; + yield return extendSelection; + } + } else + yield return extendSelection; + } else + yield return lastRect; + } + } + } + + PathFigureCollection figures = new PathFigureCollection(); + PathFigure figure; + int insertionIndex; + double lastTop, lastBottom; + double lastLeft, lastRight; + + /// + /// Adds a rectangle to the geometry. + /// + /// + /// This overload assumes that the coordinates are aligned properly + /// (see , ). + /// Use the -overload instead if the coordinates are not yet aligned. + /// + public void AddRectangle(double left, double top, double right, double bottom) + { + if (!top.IsClose(lastBottom)) { + CloseFigure(); + } + if (figure == null) { + figure = new PathFigure(); + figure.StartPoint = new Point(left, top + cornerRadius); + if (Math.Abs(left - right) > cornerRadius) { + figure.Segments.Add(MakeArc(left + cornerRadius, top, SweepDirection.Clockwise)); + figure.Segments.Add(MakeLineSegment(right - cornerRadius, top)); + figure.Segments.Add(MakeArc(right, top + cornerRadius, SweepDirection.Clockwise)); + } + figure.Segments.Add(MakeLineSegment(right, bottom - cornerRadius)); + insertionIndex = figure.Segments.Count; + //figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise)); + } else { + if (!lastRight.IsClose(right)) { + double cr = right < lastRight ? -cornerRadius : cornerRadius; + SweepDirection dir1 = right < lastRight ? SweepDirection.Clockwise : SweepDirection.Counterclockwise; + SweepDirection dir2 = right < lastRight ? SweepDirection.Counterclockwise : SweepDirection.Clockwise; + figure.Segments.Insert(insertionIndex++, MakeArc(lastRight + cr, lastBottom, dir1)); + figure.Segments.Insert(insertionIndex++, MakeLineSegment(right - cr, top)); + figure.Segments.Insert(insertionIndex++, MakeArc(right, top + cornerRadius, dir2)); + } + figure.Segments.Insert(insertionIndex++, MakeLineSegment(right, bottom - cornerRadius)); + figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft, lastTop + cornerRadius)); + if (!lastLeft.IsClose(left)) { + double cr = left < lastLeft ? cornerRadius : -cornerRadius; + SweepDirection dir1 = left < lastLeft ? SweepDirection.Counterclockwise : SweepDirection.Clockwise; + SweepDirection dir2 = left < lastLeft ? SweepDirection.Clockwise : SweepDirection.Counterclockwise; + figure.Segments.Insert(insertionIndex, MakeArc(lastLeft, lastBottom - cornerRadius, dir1)); + figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft - cr, lastBottom)); + figure.Segments.Insert(insertionIndex, MakeArc(left + cr, lastBottom, dir2)); + } + } + this.lastTop = top; + this.lastBottom = bottom; + this.lastLeft = left; + this.lastRight = right; + } + + ArcSegment MakeArc(double x, double y, SweepDirection dir) + { + ArcSegment arc = new ArcSegment( + new Point(x, y), + new Size(cornerRadius, cornerRadius), + 0, false, dir, true); + arc.Freeze(); + return arc; + } + + static LineSegment MakeLineSegment(double x, double y) + { + LineSegment ls = new LineSegment(new Point(x, y), true); + ls.Freeze(); + return ls; + } + + /// + /// Closes the current figure. + /// + public void CloseFigure() + { + if (figure != null) { + figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft, lastTop + cornerRadius)); + if (Math.Abs(lastLeft - lastRight) > cornerRadius) { + figure.Segments.Insert(insertionIndex, MakeArc(lastLeft, lastBottom - cornerRadius, SweepDirection.Clockwise)); + figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft + cornerRadius, lastBottom)); + figure.Segments.Insert(insertionIndex, MakeArc(lastRight - cornerRadius, lastBottom, SweepDirection.Clockwise)); + } + + figure.IsClosed = true; + figures.Add(figure); + figure = null; + } + } + + /// + /// Creates the geometry. + /// Returns null when the geometry is empty! + /// + public Geometry CreateGeometry() + { + CloseFigure(); + if (figures.Count != 0) { + PathGeometry g = new PathGeometry(figures); + g.Freeze(); + return g; + } else { + return null; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/CollapsedLineSection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/CollapsedLineSection.cs new file mode 100644 index 000000000..bcbf55798 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/CollapsedLineSection.cs @@ -0,0 +1,95 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Represents a collapsed line section. + /// Use the Uncollapse() method to uncollapse the section. + /// + public sealed class CollapsedLineSection + { + DocumentLine start, end; + HeightTree heightTree; + + #if DEBUG + internal string ID; + static int nextId; + #else + const string ID = ""; + #endif + + internal CollapsedLineSection(HeightTree heightTree, DocumentLine start, DocumentLine end) + { + this.heightTree = heightTree; + this.start = start; + this.end = end; + #if DEBUG + unchecked { + this.ID = " #" + (nextId++); + } + #endif + } + + /// + /// Gets if the document line is collapsed. + /// This property initially is true and turns to false when uncollapsing the section. + /// + public bool IsCollapsed { + get { return start != null; } + } + + /// + /// Gets the start line of the section. + /// When the section is uncollapsed or the text containing it is deleted, + /// this property returns null. + /// + public DocumentLine Start { + get { return start; } + internal set { start = value; } + } + + /// + /// Gets the end line of the section. + /// When the section is uncollapsed or the text containing it is deleted, + /// this property returns null. + /// + public DocumentLine End { + get { return end; } + internal set { end = value; } + } + + /// + /// Uncollapses the section. + /// This causes the Start and End properties to be set to null! + /// Does nothing if the section is already uncollapsed. + /// + public void Uncollapse() + { + if (start == null) + return; + + heightTree.Uncollapse(this); + #if DEBUG + heightTree.CheckProperties(); + #endif + + start = null; + end = null; + } + + /// + /// Gets a string representation of the collapsed section. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] + public override string ToString() + { + return "[CollapsedSection" + ID + " Start=" + (start != null ? start.LineNumber.ToString() : "null") + + " End=" + (end != null ? end.LineNumber.ToString() : "null") + "]"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColorizingTransformer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColorizingTransformer.cs new file mode 100644 index 000000000..97e2c0cea --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColorizingTransformer.cs @@ -0,0 +1,108 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Base class for that helps + /// splitting visual elements so that colors (and other text properties) can be easily assigned + /// to individual words/characters. + /// + public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect + { + /// + /// Gets the list of elements currently being transformed. + /// + protected IList CurrentElements { get; private set; } + + /// + /// implementation. + /// Sets and calls . + /// + public void Transform(ITextRunConstructionContext context, IList elements) + { + if (elements == null) + throw new ArgumentNullException("elements"); + if (this.CurrentElements != null) + throw new InvalidOperationException("Recursive Transform() call"); + this.CurrentElements = elements; + try { + Colorize(context); + } finally { + this.CurrentElements = null; + } + } + + /// + /// Performs the colorization. + /// + protected abstract void Colorize(ITextRunConstructionContext context); + + /// + /// Changes visual element properties. + /// This method accesses , so it must be called only during + /// a call. + /// This method splits s as necessary to ensure that the region + /// can be colored by setting the of whole elements, + /// and then calls the on all elements in the region. + /// + /// Start visual column of the region to change + /// End visual column of the region to change + /// Action that changes an individual . + protected void ChangeVisualElements(int visualStartColumn, int visualEndColumn, Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < CurrentElements.Count; i++) { + VisualLineElement e = CurrentElements[i]; + if (e.VisualColumn > visualEndColumn) + break; + if (e.VisualColumn < visualStartColumn && + e.VisualColumn + e.VisualLength > visualStartColumn) + { + if (e.CanSplit) { + e.Split(visualStartColumn, CurrentElements, i--); + continue; + } + } + if (e.VisualColumn >= visualStartColumn && e.VisualColumn < visualEndColumn) { + if (e.VisualColumn + e.VisualLength > visualEndColumn) { + if (e.CanSplit) { + e.Split(visualEndColumn, CurrentElements, i--); + continue; + } + } else { + action(e); + } + } + } + } + + /// + /// Called when added to a text view. + /// + protected virtual void OnAddToTextView(TextView textView) + { + } + + /// + /// Called when removed from a text view. + /// + protected virtual void OnRemoveFromTextView(TextView textView) + { + } + + void ITextViewConnect.AddToTextView(TextView textView) + { + OnAddToTextView(textView); + } + + void ITextViewConnect.RemoveFromTextView(TextView textView) + { + OnRemoveFromTextView(textView); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColumnRulerRenderer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColumnRulerRenderer.cs new file mode 100644 index 000000000..62a29dcb1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ColumnRulerRenderer.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media; + +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Renders a ruler at a certain column. + /// + sealed class ColumnRulerRenderer : IBackgroundRenderer + { + Pen pen; + int column; + TextView textView; + + public static readonly Color DefaultForeground = Colors.LightGray; + + public ColumnRulerRenderer(TextView textView) + { + if (textView == null) + throw new ArgumentNullException("textView"); + + this.pen = new Pen(new SolidColorBrush(DefaultForeground), 1); + this.pen.Freeze(); + this.textView = textView; + this.textView.BackgroundRenderers.Add(this); + } + + public KnownLayer Layer { + get { return KnownLayer.Background; } + } + + public void SetRuler(int column, Pen pen) + { + if (this.column != column) { + this.column = column; + textView.InvalidateLayer(this.Layer); + } + if (this.pen != pen) { + this.pen = pen; + textView.InvalidateLayer(this.Layer); + } + } + + public void Draw(TextView textView, System.Windows.Media.DrawingContext drawingContext) + { + if (column < 1) return; + double offset = textView.WideSpaceWidth * column; + Size pixelSize = PixelSnapHelpers.GetPixelSize(textView); + double markerXPos = PixelSnapHelpers.PixelAlign(offset, pixelSize.Width); + markerXPos -= textView.ScrollOffset.X; + Point start = new Point(markerXPos, 0); + Point end = new Point(markerXPos, Math.Max(textView.DocumentHeight, textView.ActualHeight)); + + drawingContext.DrawLine(pen, start, end); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DefaultTextRunTypographyProperties.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DefaultTextRunTypographyProperties.cs new file mode 100644 index 000000000..2ccf07e3e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DefaultTextRunTypographyProperties.cs @@ -0,0 +1,171 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media.TextFormatting; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Default implementation for TextRunTypographyProperties. + /// + public class DefaultTextRunTypographyProperties : TextRunTypographyProperties + { + /// + public override FontVariants Variants { + get { return FontVariants.Normal; } + } + + /// + public override bool StylisticSet1 { get { return false; } } + /// + public override bool StylisticSet2 { get { return false; } } + /// + public override bool StylisticSet3 { get { return false; } } + /// + public override bool StylisticSet4 { get { return false; } } + /// + public override bool StylisticSet5 { get { return false; } } + /// + public override bool StylisticSet6 { get { return false; } } + /// + public override bool StylisticSet7 { get { return false; } } + /// + public override bool StylisticSet8 { get { return false; } } + /// + public override bool StylisticSet9 { get { return false; } } + /// + public override bool StylisticSet10 { get { return false; } } + /// + public override bool StylisticSet11 { get { return false; } } + /// + public override bool StylisticSet12 { get { return false; } } + /// + public override bool StylisticSet13 { get { return false; } } + /// + public override bool StylisticSet14 { get { return false; } } + /// + public override bool StylisticSet15 { get { return false; } } + /// + public override bool StylisticSet16 { get { return false; } } + /// + public override bool StylisticSet17 { get { return false; } } + /// + public override bool StylisticSet18 { get { return false; } } + /// + public override bool StylisticSet19 { get { return false; } } + /// + public override bool StylisticSet20 { get { return false; } } + + /// + public override int StylisticAlternates { + get { return 0; } + } + + /// + public override int StandardSwashes { + get { return 0; } + } + + /// + public override bool StandardLigatures { + get { return true; } + } + + /// + public override bool SlashedZero { + get { return false; } + } + + /// + public override FontNumeralStyle NumeralStyle { + get { return FontNumeralStyle.Normal; } + } + + /// + public override FontNumeralAlignment NumeralAlignment { + get { return FontNumeralAlignment.Normal; } + } + + /// + public override bool MathematicalGreek { + get { return false; } + } + + /// + public override bool Kerning { + get { return true; } + } + + /// + public override bool HistoricalLigatures { + get { return false; } + } + + /// + public override bool HistoricalForms { + get { return false; } + } + + /// + public override FontFraction Fraction { + get { return FontFraction.Normal; } + } + + /// + public override FontEastAsianWidths EastAsianWidths { + get { return FontEastAsianWidths.Normal; } + } + + /// + public override FontEastAsianLanguage EastAsianLanguage { + get { return FontEastAsianLanguage.Normal; } + } + + /// + public override bool EastAsianExpertForms { + get { return false; } + } + + /// + public override bool DiscretionaryLigatures { + get { return false; } + } + + /// + public override int ContextualSwashes { + get { return 0; } + } + + /// + public override bool ContextualLigatures { + get { return true; } + } + + /// + public override bool ContextualAlternates { + get { return true; } + } + + /// + public override bool CaseSensitiveForms { + get { return false; } + } + + /// + public override bool CapitalSpacing { + get { return false; } + } + + /// + public override FontCapitals Capitals { + get { return FontCapitals.Normal; } + } + + /// + public override int AnnotationAlternates { + get { return 0; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DocumentColorizingTransformer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DocumentColorizingTransformer.cs new file mode 100644 index 000000000..d4032d64d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/DocumentColorizingTransformer.cs @@ -0,0 +1,81 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Base class for that helps + /// colorizing the document. Derived classes can work with document lines + /// and text offsets and this class takes care of the visual lines and visual columns. + /// + public abstract class DocumentColorizingTransformer : ColorizingTransformer + { + DocumentLine currentDocumentLine; + int firstLineStart; + int currentDocumentLineStartOffset, currentDocumentLineEndOffset; + + /// + /// Gets the current ITextRunConstructionContext. + /// + protected ITextRunConstructionContext CurrentContext { get; private set; } + + /// + protected override void Colorize(ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + this.CurrentContext = context; + + currentDocumentLine = context.VisualLine.FirstDocumentLine; + firstLineStart = currentDocumentLineStartOffset = currentDocumentLine.Offset; + currentDocumentLineEndOffset = currentDocumentLineStartOffset + currentDocumentLine.Length; + + if (context.VisualLine.FirstDocumentLine == context.VisualLine.LastDocumentLine) { + ColorizeLine(currentDocumentLine); + } else { + ColorizeLine(currentDocumentLine); + // ColorizeLine modifies the visual line elements, loop through a copy of the line elements + foreach (VisualLineElement e in context.VisualLine.Elements.ToArray()) { + int elementOffset = firstLineStart + e.RelativeTextOffset; + if (elementOffset >= currentDocumentLineEndOffset) { + currentDocumentLine = context.Document.GetLineByOffset(elementOffset); + currentDocumentLineStartOffset = currentDocumentLine.Offset; + currentDocumentLineEndOffset = currentDocumentLineStartOffset + currentDocumentLine.Length; + ColorizeLine(currentDocumentLine); + } + } + } + currentDocumentLine = null; + this.CurrentContext = null; + } + + /// + /// Override this method to colorize an individual document line. + /// + protected abstract void ColorizeLine(DocumentLine line); + + /// + /// Changes a part of the current document line. + /// + /// Start offset of the region to change + /// End offset of the region to change + /// Action that changes an individual . + protected void ChangeLinePart(int startOffset, int endOffset, Action action) + { + if (startOffset < currentDocumentLineStartOffset || startOffset > currentDocumentLineEndOffset) + throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between " + currentDocumentLineStartOffset + " and " + currentDocumentLineEndOffset); + if (endOffset < startOffset || endOffset > currentDocumentLineEndOffset) + throw new ArgumentOutOfRangeException("endOffset", endOffset, "Value must be between " + startOffset + " and " + currentDocumentLineEndOffset); + VisualLine vl = this.CurrentContext.VisualLine; + int visualStart = vl.GetVisualColumn(startOffset - firstLineStart); + int visualEnd = vl.GetVisualColumn(endOffset - firstLineStart); + if (visualStart < visualEnd) { + ChangeVisualElements(visualStart, visualEnd, action); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/FormattedTextElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/FormattedTextElement.cs new file mode 100644 index 000000000..168dd170d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/FormattedTextElement.cs @@ -0,0 +1,207 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Formatted text (not normal document text). + /// This is used as base class for various VisualLineElements that are displayed using a + /// FormattedText, for example newline markers or collapsed folding sections. + /// + public class FormattedTextElement : VisualLineElement + { + internal readonly FormattedText formattedText; + internal string text; + internal TextLine textLine; + + /// + /// Creates a new FormattedTextElement that displays the specified text + /// and occupies the specified length in the document. + /// + public FormattedTextElement(string text, int documentLength) : base(1, documentLength) + { + if (text == null) + throw new ArgumentNullException("text"); + this.text = text; + this.BreakBefore = LineBreakCondition.BreakPossible; + this.BreakAfter = LineBreakCondition.BreakPossible; + } + + /// + /// Creates a new FormattedTextElement that displays the specified text + /// and occupies the specified length in the document. + /// + public FormattedTextElement(TextLine text, int documentLength) : base(1, documentLength) + { + if (text == null) + throw new ArgumentNullException("text"); + this.textLine = text; + this.BreakBefore = LineBreakCondition.BreakPossible; + this.BreakAfter = LineBreakCondition.BreakPossible; + } + + /// + /// Creates a new FormattedTextElement that displays the specified text + /// and occupies the specified length in the document. + /// + public FormattedTextElement(FormattedText text, int documentLength) : base(1, documentLength) + { + if (text == null) + throw new ArgumentNullException("text"); + this.formattedText = text; + this.BreakBefore = LineBreakCondition.BreakPossible; + this.BreakAfter = LineBreakCondition.BreakPossible; + } + + /// + /// Gets/sets the line break condition before the element. + /// The default is 'BreakPossible'. + /// + public LineBreakCondition BreakBefore { get; set; } + + /// + /// Gets/sets the line break condition after the element. + /// The default is 'BreakPossible'. + /// + public LineBreakCondition BreakAfter { get; set; } + + /// + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + if (textLine == null) { + var formatter = TextFormatterFactory.Create(context.TextView); + textLine = PrepareText(formatter, this.text, this.TextRunProperties); + this.text = null; + } + return new FormattedTextRun(this, this.TextRunProperties); + } + + /// + /// Constructs a TextLine from a simple text. + /// + public static TextLine PrepareText(TextFormatter formatter, string text, TextRunProperties properties) + { + if (formatter == null) + throw new ArgumentNullException("formatter"); + if (text == null) + throw new ArgumentNullException("text"); + if (properties == null) + throw new ArgumentNullException("properties"); + return formatter.FormatLine( + new SimpleTextSource(text, properties), + 0, + 32000, + new VisualLineTextParagraphProperties { + defaultTextRunProperties = properties, + textWrapping = TextWrapping.NoWrap, + tabSize = 40 + }, + null); + } + } + + /// + /// This is the TextRun implementation used by the class. + /// + public class FormattedTextRun : TextEmbeddedObject + { + readonly FormattedTextElement element; + TextRunProperties properties; + + /// + /// Creates a new FormattedTextRun. + /// + public FormattedTextRun(FormattedTextElement element, TextRunProperties properties) + { + if (element == null) + throw new ArgumentNullException("element"); + if (properties == null) + throw new ArgumentNullException("properties"); + this.properties = properties; + this.element = element; + } + + /// + /// Gets the element for which the FormattedTextRun was created. + /// + public FormattedTextElement Element { + get { return element; } + } + + /// + public override LineBreakCondition BreakBefore { + get { return element.BreakBefore; } + } + + /// + public override LineBreakCondition BreakAfter { + get { return element.BreakAfter; } + } + + /// + public override bool HasFixedSize { + get { return true; } + } + + /// + public override CharacterBufferReference CharacterBufferReference { + get { return new CharacterBufferReference(); } + } + + /// + public override int Length { + get { return element.VisualLength; } + } + + /// + public override TextRunProperties Properties { + get { return properties; } + } + + /// + public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth) + { + var formattedText = element.formattedText; + if (formattedText != null) { + return new TextEmbeddedObjectMetrics(formattedText.WidthIncludingTrailingWhitespace, + formattedText.Height, + formattedText.Baseline); + } else { + var text = element.textLine; + return new TextEmbeddedObjectMetrics(text.WidthIncludingTrailingWhitespace, + text.Height, + text.Baseline); + } + } + + /// + public override Rect ComputeBoundingBox(bool rightToLeft, bool sideways) + { + var formattedText = element.formattedText; + if (formattedText != null) { + return new Rect(0, 0, formattedText.WidthIncludingTrailingWhitespace, formattedText.Height); + } else { + var text = element.textLine; + return new Rect(0, 0, text.WidthIncludingTrailingWhitespace, text.Height); + } + } + + /// + public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways) + { + if (element.formattedText != null) { + origin.Y -= element.formattedText.Baseline; + drawingContext.DrawText(element.formattedText, origin); + } else { + origin.Y -= element.textLine.Baseline; + element.textLine.Draw(drawingContext, origin, InvertAxes.None); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/GlobalTextRunProperties.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/GlobalTextRunProperties.cs new file mode 100644 index 000000000..f0f40c882 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/GlobalTextRunProperties.cs @@ -0,0 +1,30 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +namespace Tango.Scripting.Editors.Rendering +{ + sealed class GlobalTextRunProperties : TextRunProperties + { + internal Typeface typeface; + internal double fontRenderingEmSize; + internal Brush foregroundBrush; + private Brush backgroundBrush1; + internal System.Globalization.CultureInfo cultureInfo; + + public override Typeface Typeface { get { return typeface; } } + public override double FontRenderingEmSize { get { return fontRenderingEmSize; } } + public override double FontHintingEmSize { get { return fontRenderingEmSize; } } + public override TextDecorationCollection TextDecorations { get { return null; } } + public override Brush ForegroundBrush { get { return foregroundBrush; } } + public override Brush BackgroundBrush { get { return BackgroundBrush1; } } + public override System.Globalization.CultureInfo CultureInfo { get { return cultureInfo; } } + public override TextEffectCollection TextEffects { get { return null; } } + + internal Brush BackgroundBrush1 { get => backgroundBrush1; set => backgroundBrush1 = value; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTree.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTree.cs new file mode 100644 index 000000000..6751af8fa --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTree.cs @@ -0,0 +1,1092 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Red-black tree similar to DocumentLineTree, augmented with collapsing and height data. + /// + sealed class HeightTree : ILineTracker, IDisposable + { + // TODO: Optimize this. This tree takes alot of memory. + // (56 bytes for HeightTreeNode + // We should try to get rid of the dictionary and find height nodes per index. (DONE!) + // And we might do much better by compressing lines with the same height into a single node. + // That would also improve load times because we would always start with just a single node. + + /* Idea: + class NewHeightTreeNode { + int totalCount; // =count+left.count+right.count + int count; // one node can represent multiple lines + double height; // height of each line in this node + double totalHeight; // =(collapsedSections!=null?0:height*count) + left.totalHeight + right.totalHeight + List collapsedSections; // sections holding this line collapsed + // no "nodeCollapsedSections"/"totalCollapsedSections": + NewHeightTreeNode left, right, parent; + bool color; + } + totalCollapsedSections: are hard to update and not worth the effort. O(n log n) isn't too bad for + collapsing/uncollapsing, especially when compression reduces the n. + */ + + #region Constructor + readonly TextDocument document; + HeightTreeNode root; + WeakLineTracker weakLineTracker; + + public HeightTree(TextDocument document, double defaultLineHeight) + { + this.document = document; + weakLineTracker = WeakLineTracker.Register(document, this); + this.DefaultLineHeight = defaultLineHeight; + RebuildDocument(); + } + + public void Dispose() + { + if (weakLineTracker != null) + weakLineTracker.Deregister(); + this.root = null; + this.weakLineTracker = null; + } + + double defaultLineHeight; + + public double DefaultLineHeight { + get { return defaultLineHeight; } + set { + double oldValue = defaultLineHeight; + if (oldValue == value) + return; + defaultLineHeight = value; + // update the stored value in all nodes: + foreach (var node in AllNodes) { + if (node.lineNode.height == oldValue) { + node.lineNode.height = value; + UpdateAugmentedData(node, UpdateAfterChildrenChangeRecursionMode.IfRequired); + } + } + } + } + + HeightTreeNode GetNode(DocumentLine ls) + { + return GetNodeByIndex(ls.LineNumber - 1); + } + #endregion + + #region RebuildDocument + void ILineTracker.SetLineLength(DocumentLine ls, int newTotalLength) + { + } + + /// + /// Rebuild the tree, in O(n). + /// + public void RebuildDocument() + { + foreach (CollapsedLineSection s in GetAllCollapsedSections()) { + s.Start = null; + s.End = null; + } + + HeightTreeNode[] nodes = new HeightTreeNode[document.LineCount]; + int lineNumber = 0; + foreach (DocumentLine ls in document.Lines) { + nodes[lineNumber++] = new HeightTreeNode(ls, defaultLineHeight); + } + Debug.Assert(nodes.Length > 0); + // now build the corresponding balanced tree + int height = DocumentLineTree.GetTreeHeight(nodes.Length); + Debug.WriteLine("HeightTree will have height: " + height); + root = BuildTree(nodes, 0, nodes.Length, height); + root.color = BLACK; + #if DEBUG + CheckProperties(); + #endif + } + + /// + /// build a tree from a list of nodes + /// + HeightTreeNode BuildTree(HeightTreeNode[] nodes, int start, int end, int subtreeHeight) + { + Debug.Assert(start <= end); + if (start == end) { + return null; + } + int middle = (start + end) / 2; + HeightTreeNode node = nodes[middle]; + node.left = BuildTree(nodes, start, middle, subtreeHeight - 1); + node.right = BuildTree(nodes, middle + 1, end, subtreeHeight - 1); + if (node.left != null) node.left.parent = node; + if (node.right != null) node.right.parent = node; + if (subtreeHeight == 1) + node.color = RED; + UpdateAugmentedData(node, UpdateAfterChildrenChangeRecursionMode.None); + return node; + } + #endregion + + #region Insert/Remove lines + void ILineTracker.BeforeRemoveLine(DocumentLine line) + { + HeightTreeNode node = GetNode(line); + if (node.lineNode.collapsedSections != null) { + foreach (CollapsedLineSection cs in node.lineNode.collapsedSections.ToArray()) { + if (cs.Start == line && cs.End == line) { + cs.Start = null; + cs.End = null; + } else if (cs.Start == line) { + Uncollapse(cs); + cs.Start = line.NextLine; + AddCollapsedSection(cs, cs.End.LineNumber - cs.Start.LineNumber + 1); + } else if (cs.End == line) { + Uncollapse(cs); + cs.End = line.PreviousLine; + AddCollapsedSection(cs, cs.End.LineNumber - cs.Start.LineNumber + 1); + } + } + } + BeginRemoval(); + RemoveNode(node); + // clear collapsedSections from removed line: prevent damage if removed line is in "nodesToCheckForMerging" + node.lineNode.collapsedSections = null; + EndRemoval(); + } + +// void ILineTracker.AfterRemoveLine(DocumentLine line) +// { +// +// } + + void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) + { + InsertAfter(GetNode(insertionPos), newLine); + #if DEBUG + CheckProperties(); + #endif + } + + HeightTreeNode InsertAfter(HeightTreeNode node, DocumentLine newLine) + { + HeightTreeNode newNode = new HeightTreeNode(newLine, defaultLineHeight); + if (node.right == null) { + if (node.lineNode.collapsedSections != null) { + // we are inserting directly after node - so copy all collapsedSections + // that do not end at node. + foreach (CollapsedLineSection cs in node.lineNode.collapsedSections) { + if (cs.End != node.documentLine) + newNode.AddDirectlyCollapsed(cs); + } + } + InsertAsRight(node, newNode); + } else { + node = node.right.LeftMost; + if (node.lineNode.collapsedSections != null) { + // we are inserting directly before node - so copy all collapsedSections + // that do not start at node. + foreach (CollapsedLineSection cs in node.lineNode.collapsedSections) { + if (cs.Start != node.documentLine) + newNode.AddDirectlyCollapsed(cs); + } + } + InsertAsLeft(node, newNode); + } + return newNode; + } + #endregion + + #region Rotation callbacks + enum UpdateAfterChildrenChangeRecursionMode + { + None, + IfRequired, + WholeBranch + } + + static void UpdateAfterChildrenChange(HeightTreeNode node) + { + UpdateAugmentedData(node, UpdateAfterChildrenChangeRecursionMode.IfRequired); + } + + static void UpdateAugmentedData(HeightTreeNode node, UpdateAfterChildrenChangeRecursionMode mode) + { + int totalCount = 1; + double totalHeight = node.lineNode.TotalHeight; + if (node.left != null) { + totalCount += node.left.totalCount; + totalHeight += node.left.totalHeight; + } + if (node.right != null) { + totalCount += node.right.totalCount; + totalHeight += node.right.totalHeight; + } + if (node.IsDirectlyCollapsed) + totalHeight = 0; + if (totalCount != node.totalCount + || !totalHeight.IsClose(node.totalHeight) + || mode == UpdateAfterChildrenChangeRecursionMode.WholeBranch) + { + node.totalCount = totalCount; + node.totalHeight = totalHeight; + if (node.parent != null && mode != UpdateAfterChildrenChangeRecursionMode.None) + UpdateAugmentedData(node.parent, mode); + } + } + + void UpdateAfterRotateLeft(HeightTreeNode node) + { + // node = old parent + // node.parent = pivot, new parent + var collapsedP = node.parent.collapsedSections; + var collapsedQ = node.collapsedSections; + // move collapsedSections from old parent to new parent + node.parent.collapsedSections = collapsedQ; + node.collapsedSections = null; + // split the collapsedSections from the new parent into its old children: + if (collapsedP != null) { + foreach (CollapsedLineSection cs in collapsedP) { + if (node.parent.right != null) + node.parent.right.AddDirectlyCollapsed(cs); + node.parent.lineNode.AddDirectlyCollapsed(cs); + if (node.right != null) + node.right.AddDirectlyCollapsed(cs); + } + } + MergeCollapsedSectionsIfPossible(node); + + UpdateAfterChildrenChange(node); + + // not required: rotations only happen on insertions/deletions + // -> totalCount changes -> the parent is always updated + //UpdateAfterChildrenChange(node.parent); + } + + void UpdateAfterRotateRight(HeightTreeNode node) + { + // node = old parent + // node.parent = pivot, new parent + var collapsedP = node.parent.collapsedSections; + var collapsedQ = node.collapsedSections; + // move collapsedSections from old parent to new parent + node.parent.collapsedSections = collapsedQ; + node.collapsedSections = null; + // split the collapsedSections from the new parent into its old children: + if (collapsedP != null) { + foreach (CollapsedLineSection cs in collapsedP) { + if (node.parent.left != null) + node.parent.left.AddDirectlyCollapsed(cs); + node.parent.lineNode.AddDirectlyCollapsed(cs); + if (node.left != null) + node.left.AddDirectlyCollapsed(cs); + } + } + MergeCollapsedSectionsIfPossible(node); + + UpdateAfterChildrenChange(node); + + // not required: rotations only happen on insertions/deletions + // -> totalCount changes -> the parent is always updated + //UpdateAfterChildrenChange(node.parent); + } + + // node removal: + // a node in the middle of the tree is removed as following: + // its successor is removed + // it is replaced with its successor + + void BeforeNodeRemove(HeightTreeNode removedNode) + { + Debug.Assert(removedNode.left == null || removedNode.right == null); + + var collapsed = removedNode.collapsedSections; + if (collapsed != null) { + HeightTreeNode childNode = removedNode.left ?? removedNode.right; + if (childNode != null) { + foreach (CollapsedLineSection cs in collapsed) + childNode.AddDirectlyCollapsed(cs); + } + } + if (removedNode.parent != null) + MergeCollapsedSectionsIfPossible(removedNode.parent); + } + + void BeforeNodeReplace(HeightTreeNode removedNode, HeightTreeNode newNode, HeightTreeNode newNodeOldParent) + { + Debug.Assert(removedNode != null); + Debug.Assert(newNode != null); + while (newNodeOldParent != removedNode) { + if (newNodeOldParent.collapsedSections != null) { + foreach (CollapsedLineSection cs in newNodeOldParent.collapsedSections) { + newNode.lineNode.AddDirectlyCollapsed(cs); + } + } + newNodeOldParent = newNodeOldParent.parent; + } + if (newNode.collapsedSections != null) { + foreach (CollapsedLineSection cs in newNode.collapsedSections) { + newNode.lineNode.AddDirectlyCollapsed(cs); + } + } + newNode.collapsedSections = removedNode.collapsedSections; + MergeCollapsedSectionsIfPossible(newNode); + } + + bool inRemoval; + List nodesToCheckForMerging; + + void BeginRemoval() + { + Debug.Assert(!inRemoval); + if (nodesToCheckForMerging == null) { + nodesToCheckForMerging = new List(); + } + inRemoval = true; + } + + void EndRemoval() + { + Debug.Assert(inRemoval); + inRemoval = false; + foreach (HeightTreeNode node in nodesToCheckForMerging) { + MergeCollapsedSectionsIfPossible(node); + } + nodesToCheckForMerging.Clear(); + } + + void MergeCollapsedSectionsIfPossible(HeightTreeNode node) + { + Debug.Assert(node != null); + if (inRemoval) { + nodesToCheckForMerging.Add(node); + return; + } + // now check if we need to merge collapsedSections together + bool merged = false; + var collapsedL = node.lineNode.collapsedSections; + if (collapsedL != null) { + for (int i = collapsedL.Count - 1; i >= 0; i--) { + CollapsedLineSection cs = collapsedL[i]; + if (cs.Start == node.documentLine || cs.End == node.documentLine) + continue; + if (node.left == null + || (node.left.collapsedSections != null && node.left.collapsedSections.Contains(cs))) + { + if (node.right == null + || (node.right.collapsedSections != null && node.right.collapsedSections.Contains(cs))) + { + // all children of node contain cs: -> merge! + if (node.left != null) node.left.RemoveDirectlyCollapsed(cs); + if (node.right != null) node.right.RemoveDirectlyCollapsed(cs); + collapsedL.RemoveAt(i); + node.AddDirectlyCollapsed(cs); + merged = true; + } + } + } + if (collapsedL.Count == 0) + node.lineNode.collapsedSections = null; + } + if (merged && node.parent != null) { + MergeCollapsedSectionsIfPossible(node.parent); + } + } + #endregion + + #region GetNodeBy... / Get...FromNode + HeightTreeNode GetNodeByIndex(int index) + { + Debug.Assert(index >= 0); + Debug.Assert(index < root.totalCount); + HeightTreeNode node = root; + while (true) { + if (node.left != null && index < node.left.totalCount) { + node = node.left; + } else { + if (node.left != null) { + index -= node.left.totalCount; + } + if (index == 0) + return node; + index--; + node = node.right; + } + } + } + + HeightTreeNode GetNodeByVisualPosition(double position) + { + HeightTreeNode node = root; + while (true) { + double positionAfterLeft = position; + if (node.left != null) { + positionAfterLeft -= node.left.totalHeight; + if (positionAfterLeft < 0) { + // Descend into left + node = node.left; + continue; + } + } + double positionBeforeRight = positionAfterLeft - node.lineNode.TotalHeight; + if (positionBeforeRight < 0) { + // Found the correct node + return node; + } + if (node.right == null || node.right.totalHeight == 0) { + // Can happen when position>node.totalHeight, + // i.e. at the end of the document, or due to rounding errors in previous loop iterations. + + // If node.lineNode isn't collapsed, return that. + // Also return node.lineNode if there is no previous node that we could return instead. + if (node.lineNode.TotalHeight > 0 || node.left == null) + return node; + // Otherwise, descend into left (find the last non-collapsed node) + node = node.left; + } else { + // Descend into right + position = positionBeforeRight; + node = node.right; + } + } + } + + static double GetVisualPositionFromNode(HeightTreeNode node) + { + double position = (node.left != null) ? node.left.totalHeight : 0; + while (node.parent != null) { + if (node.IsDirectlyCollapsed) + position = 0; + if (node == node.parent.right) { + if (node.parent.left != null) + position += node.parent.left.totalHeight; + position += node.parent.lineNode.TotalHeight; + } + node = node.parent; + } + return position; + } + #endregion + + #region Public methods + public DocumentLine GetLineByNumber(int number) + { + return GetNodeByIndex(number - 1).documentLine; + } + + public DocumentLine GetLineByVisualPosition(double position) + { + return GetNodeByVisualPosition(position).documentLine; + } + + public double GetVisualPosition(DocumentLine line) + { + return GetVisualPositionFromNode(GetNode(line)); + } + + public double GetHeight(DocumentLine line) + { + return GetNode(line).lineNode.height; + } + + public void SetHeight(DocumentLine line, double val) + { + var node = GetNode(line); + node.lineNode.height = val; + UpdateAfterChildrenChange(node); + } + + public bool GetIsCollapsed(int lineNumber) + { + var node = GetNodeByIndex(lineNumber - 1); + return node.lineNode.IsDirectlyCollapsed || GetIsCollapedFromNode(node); + } + + /// + /// Collapses the specified text section. + /// Runtime: O(log n) + /// + public CollapsedLineSection CollapseText(DocumentLine start, DocumentLine end) + { + if (!document.Lines.Contains(start)) + throw new ArgumentException("Line is not part of this document", "start"); + if (!document.Lines.Contains(end)) + throw new ArgumentException("Line is not part of this document", "end"); + int length = end.LineNumber - start.LineNumber + 1; + if (length < 0) + throw new ArgumentException("start must be a line before end"); + CollapsedLineSection section = new CollapsedLineSection(this, start, end); + AddCollapsedSection(section, length); + #if DEBUG + CheckProperties(); + #endif + return section; + } + #endregion + + #region LineCount & TotalHeight + public int LineCount { + get { + return root.totalCount; + } + } + + public double TotalHeight { + get { + return root.totalHeight; + } + } + #endregion + + #region GetAllCollapsedSections + IEnumerable AllNodes { + get { + if (root != null) { + HeightTreeNode node = root.LeftMost; + while (node != null) { + yield return node; + node = node.Successor; + } + } + } + } + + internal IEnumerable GetAllCollapsedSections() + { + List emptyCSList = new List(); + return System.Linq.Enumerable.Distinct( + System.Linq.Enumerable.SelectMany( + AllNodes, node => System.Linq.Enumerable.Concat(node.lineNode.collapsedSections ?? emptyCSList, + node.collapsedSections ?? emptyCSList) + )); + } + #endregion + + #region CheckProperties + #if DEBUG + [Conditional("DATACONSISTENCYTEST")] + internal void CheckProperties() + { + CheckProperties(root); + + foreach (CollapsedLineSection cs in GetAllCollapsedSections()) { + Debug.Assert(GetNode(cs.Start).lineNode.collapsedSections.Contains(cs)); + Debug.Assert(GetNode(cs.End).lineNode.collapsedSections.Contains(cs)); + int endLine = cs.End.LineNumber; + for (int i = cs.Start.LineNumber; i <= endLine; i++) { + CheckIsInSection(cs, GetLineByNumber(i)); + } + } + + // check red-black property: + int blackCount = -1; + CheckNodeProperties(root, null, RED, 0, ref blackCount); + } + + void CheckIsInSection(CollapsedLineSection cs, DocumentLine line) + { + HeightTreeNode node = GetNode(line); + if (node.lineNode.collapsedSections != null && node.lineNode.collapsedSections.Contains(cs)) + return; + while (node != null) { + if (node.collapsedSections != null && node.collapsedSections.Contains(cs)) + return; + node = node.parent; + } + throw new InvalidOperationException(cs + " not found for line " + line); + } + + void CheckProperties(HeightTreeNode node) + { + int totalCount = 1; + double totalHeight = node.lineNode.TotalHeight; + if (node.lineNode.IsDirectlyCollapsed) + Debug.Assert(node.lineNode.collapsedSections.Count > 0); + if (node.left != null) { + CheckProperties(node.left); + totalCount += node.left.totalCount; + totalHeight += node.left.totalHeight; + + CheckAllContainedIn(node.left.collapsedSections, node.lineNode.collapsedSections); + } + if (node.right != null) { + CheckProperties(node.right); + totalCount += node.right.totalCount; + totalHeight += node.right.totalHeight; + + CheckAllContainedIn(node.right.collapsedSections, node.lineNode.collapsedSections); + } + if (node.left != null && node.right != null) { + if (node.left.collapsedSections != null && node.right.collapsedSections != null) { + var intersection = System.Linq.Enumerable.Intersect(node.left.collapsedSections, node.right.collapsedSections); + Debug.Assert(System.Linq.Enumerable.Count(intersection) == 0); + } + } + if (node.IsDirectlyCollapsed) { + Debug.Assert(node.collapsedSections.Count > 0); + totalHeight = 0; + } + Debug.Assert(node.totalCount == totalCount); + Debug.Assert(node.totalHeight.IsClose(totalHeight)); + } + + /// + /// Checks that all elements in list1 are contained in list2. + /// + static void CheckAllContainedIn(IEnumerable list1, ICollection list2) + { + if (list1 == null) list1 = new List(); + if (list2 == null) list2 = new List(); + foreach (CollapsedLineSection cs in list1) { + Debug.Assert(list2.Contains(cs)); + } + } + + /* + 1. A node is either red or black. + 2. The root is black. + 3. All leaves are black. (The leaves are the NIL children.) + 4. Both children of every red node are black. (So every red node must have a black parent.) + 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) + */ + void CheckNodeProperties(HeightTreeNode node, HeightTreeNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) + { + if (node == null) return; + + Debug.Assert(node.parent == parentNode); + + if (parentColor == RED) { + Debug.Assert(node.color == BLACK); + } + if (node.color == BLACK) { + blackCount++; + } + if (node.left == null && node.right == null) { + // node is a leaf node: + if (expectedBlackCount == -1) + expectedBlackCount = blackCount; + else + Debug.Assert(expectedBlackCount == blackCount); + } + CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); + CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public string GetTreeAsString() + { + StringBuilder b = new StringBuilder(); + AppendTreeToString(root, b, 0); + return b.ToString(); + } + + static void AppendTreeToString(HeightTreeNode node, StringBuilder b, int indent) + { + if (node.color == RED) + b.Append("RED "); + else + b.Append("BLACK "); + b.AppendLine(node.ToString()); + indent += 2; + if (node.left != null) { + b.Append(' ', indent); + b.Append("L: "); + AppendTreeToString(node.left, b, indent); + } + if (node.right != null) { + b.Append(' ', indent); + b.Append("R: "); + AppendTreeToString(node.right, b, indent); + } + } + #endif + #endregion + + #region Red/Black Tree + const bool RED = true; + const bool BLACK = false; + + void InsertAsLeft(HeightTreeNode parentNode, HeightTreeNode newNode) + { + Debug.Assert(parentNode.left == null); + parentNode.left = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAfterChildrenChange(parentNode); + FixTreeOnInsert(newNode); + } + + void InsertAsRight(HeightTreeNode parentNode, HeightTreeNode newNode) + { + Debug.Assert(parentNode.right == null); + parentNode.right = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAfterChildrenChange(parentNode); + FixTreeOnInsert(newNode); + } + + void FixTreeOnInsert(HeightTreeNode node) + { + Debug.Assert(node != null); + Debug.Assert(node.color == RED); + Debug.Assert(node.left == null || node.left.color == BLACK); + Debug.Assert(node.right == null || node.right.color == BLACK); + + HeightTreeNode parentNode = node.parent; + if (parentNode == null) { + // we inserted in the root -> the node must be black + // since this is a root node, making the node black increments the number of black nodes + // on all paths by one, so it is still the same for all paths. + node.color = BLACK; + return; + } + if (parentNode.color == BLACK) { + // if the parent node where we inserted was black, our red node is placed correctly. + // since we inserted a red node, the number of black nodes on each path is unchanged + // -> the tree is still balanced + return; + } + // parentNode is red, so there is a conflict here! + + // because the root is black, parentNode is not the root -> there is a grandparent node + HeightTreeNode grandparentNode = parentNode.parent; + HeightTreeNode uncleNode = Sibling(parentNode); + if (uncleNode != null && uncleNode.color == RED) { + parentNode.color = BLACK; + uncleNode.color = BLACK; + grandparentNode.color = RED; + FixTreeOnInsert(grandparentNode); + return; + } + // now we know: parent is red but uncle is black + // First rotation: + if (node == parentNode.right && parentNode == grandparentNode.left) { + RotateLeft(parentNode); + node = node.left; + } else if (node == parentNode.left && parentNode == grandparentNode.right) { + RotateRight(parentNode); + node = node.right; + } + // because node might have changed, reassign variables: + parentNode = node.parent; + grandparentNode = parentNode.parent; + + // Now recolor a bit: + parentNode.color = BLACK; + grandparentNode.color = RED; + // Second rotation: + if (node == parentNode.left && parentNode == grandparentNode.left) { + RotateRight(grandparentNode); + } else { + // because of the first rotation, this is guaranteed: + Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); + RotateLeft(grandparentNode); + } + } + + void RemoveNode(HeightTreeNode removedNode) + { + if (removedNode.left != null && removedNode.right != null) { + // replace removedNode with it's in-order successor + + HeightTreeNode leftMost = removedNode.right.LeftMost; + HeightTreeNode parentOfLeftMost = leftMost.parent; + RemoveNode(leftMost); // remove leftMost from its current location + + BeforeNodeReplace(removedNode, leftMost, parentOfLeftMost); + // and overwrite the removedNode with it + ReplaceNode(removedNode, leftMost); + leftMost.left = removedNode.left; + if (leftMost.left != null) leftMost.left.parent = leftMost; + leftMost.right = removedNode.right; + if (leftMost.right != null) leftMost.right.parent = leftMost; + leftMost.color = removedNode.color; + + UpdateAfterChildrenChange(leftMost); + if (leftMost.parent != null) UpdateAfterChildrenChange(leftMost.parent); + return; + } + + // now either removedNode.left or removedNode.right is null + // get the remaining child + HeightTreeNode parentNode = removedNode.parent; + HeightTreeNode childNode = removedNode.left ?? removedNode.right; + BeforeNodeRemove(removedNode); + ReplaceNode(removedNode, childNode); + if (parentNode != null) UpdateAfterChildrenChange(parentNode); + if (removedNode.color == BLACK) { + if (childNode != null && childNode.color == RED) { + childNode.color = BLACK; + } else { + FixTreeOnDelete(childNode, parentNode); + } + } + } + + void FixTreeOnDelete(HeightTreeNode node, HeightTreeNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (parentNode == null) + return; + + // warning: node may be null + HeightTreeNode sibling = Sibling(node, parentNode); + if (sibling.color == RED) { + parentNode.color = RED; + sibling.color = BLACK; + if (node == parentNode.left) { + RotateLeft(parentNode); + } else { + RotateRight(parentNode); + } + + sibling = Sibling(node, parentNode); // update value of sibling after rotation + } + + if (parentNode.color == BLACK + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + FixTreeOnDelete(parentNode, parentNode.parent); + return; + } + + if (parentNode.color == RED + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + parentNode.color = BLACK; + return; + } + + if (node == parentNode.left && + sibling.color == BLACK && + GetColor(sibling.left) == RED && + GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + sibling.left.color = BLACK; + RotateRight(sibling); + } + else if (node == parentNode.right && + sibling.color == BLACK && + GetColor(sibling.right) == RED && + GetColor(sibling.left) == BLACK) + { + sibling.color = RED; + sibling.right.color = BLACK; + RotateLeft(sibling); + } + sibling = Sibling(node, parentNode); // update value of sibling after rotation + + sibling.color = parentNode.color; + parentNode.color = BLACK; + if (node == parentNode.left) { + if (sibling.right != null) { + Debug.Assert(sibling.right.color == RED); + sibling.right.color = BLACK; + } + RotateLeft(parentNode); + } else { + if (sibling.left != null) { + Debug.Assert(sibling.left.color == RED); + sibling.left.color = BLACK; + } + RotateRight(parentNode); + } + } + + void ReplaceNode(HeightTreeNode replacedNode, HeightTreeNode newNode) + { + if (replacedNode.parent == null) { + Debug.Assert(replacedNode == root); + root = newNode; + } else { + if (replacedNode.parent.left == replacedNode) + replacedNode.parent.left = newNode; + else + replacedNode.parent.right = newNode; + } + if (newNode != null) { + newNode.parent = replacedNode.parent; + } + replacedNode.parent = null; + } + + void RotateLeft(HeightTreeNode p) + { + // let q be p's right child + HeightTreeNode q = p.right; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's right child to be q's left child + p.right = q.left; + if (p.right != null) p.right.parent = p; + // set q's left child to be p + q.left = p; + p.parent = q; + UpdateAfterRotateLeft(p); + } + + void RotateRight(HeightTreeNode p) + { + // let q be p's left child + HeightTreeNode q = p.left; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's left child to be q's right child + p.left = q.right; + if (p.left != null) p.left.parent = p; + // set q's right child to be p + q.right = p; + p.parent = q; + UpdateAfterRotateRight(p); + } + + static HeightTreeNode Sibling(HeightTreeNode node) + { + if (node == node.parent.left) + return node.parent.right; + else + return node.parent.left; + } + + static HeightTreeNode Sibling(HeightTreeNode node, HeightTreeNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (node == parentNode.left) + return parentNode.right; + else + return parentNode.left; + } + + static bool GetColor(HeightTreeNode node) + { + return node != null ? node.color : BLACK; + } + #endregion + + #region Collapsing support + static bool GetIsCollapedFromNode(HeightTreeNode node) + { + while (node != null) { + if (node.IsDirectlyCollapsed) + return true; + node = node.parent; + } + return false; + } + + internal void AddCollapsedSection(CollapsedLineSection section, int sectionLength) + { + AddRemoveCollapsedSection(section, sectionLength, true); + } + + void AddRemoveCollapsedSection(CollapsedLineSection section, int sectionLength, bool add) + { + Debug.Assert(sectionLength > 0); + + HeightTreeNode node = GetNode(section.Start); + // Go up in the tree. + while (true) { + // Mark all middle nodes as collapsed + if (add) + node.lineNode.AddDirectlyCollapsed(section); + else + node.lineNode.RemoveDirectlyCollapsed(section); + sectionLength -= 1; + if (sectionLength == 0) { + // we are done! + Debug.Assert(node.documentLine == section.End); + break; + } + // Mark all right subtrees as collapsed. + if (node.right != null) { + if (node.right.totalCount < sectionLength) { + if (add) + node.right.AddDirectlyCollapsed(section); + else + node.right.RemoveDirectlyCollapsed(section); + sectionLength -= node.right.totalCount; + } else { + // mark partially into the right subtree: go down the right subtree. + AddRemoveCollapsedSectionDown(section, node.right, sectionLength, add); + break; + } + } + // go up to the next node + HeightTreeNode parentNode = node.parent; + Debug.Assert(parentNode != null); + while (parentNode.right == node) { + node = parentNode; + parentNode = node.parent; + Debug.Assert(parentNode != null); + } + node = parentNode; + } + UpdateAugmentedData(GetNode(section.Start), UpdateAfterChildrenChangeRecursionMode.WholeBranch); + UpdateAugmentedData(GetNode(section.End), UpdateAfterChildrenChangeRecursionMode.WholeBranch); + } + + static void AddRemoveCollapsedSectionDown(CollapsedLineSection section, HeightTreeNode node, int sectionLength, bool add) + { + while (true) { + if (node.left != null) { + if (node.left.totalCount < sectionLength) { + // mark left subtree + if (add) + node.left.AddDirectlyCollapsed(section); + else + node.left.RemoveDirectlyCollapsed(section); + sectionLength -= node.left.totalCount; + } else { + // mark only inside the left subtree + node = node.left; + Debug.Assert(node != null); + continue; + } + } + if (add) + node.lineNode.AddDirectlyCollapsed(section); + else + node.lineNode.RemoveDirectlyCollapsed(section); + sectionLength -= 1; + if (sectionLength == 0) { + // done! + Debug.Assert(node.documentLine == section.End); + break; + } + // mark inside right subtree: + node = node.right; + Debug.Assert(node != null); + } + } + + public void Uncollapse(CollapsedLineSection section) + { + int sectionLength = section.End.LineNumber - section.Start.LineNumber + 1; + AddRemoveCollapsedSection(section, sectionLength, false); + // do not call CheckProperties() in here - Uncollapse is also called during line removals + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeLineNode.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeLineNode.cs new file mode 100644 index 000000000..459f2a56a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeLineNode.cs @@ -0,0 +1,49 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Tango.Scripting.Editors.Rendering +{ + struct HeightTreeLineNode + { + internal HeightTreeLineNode(double height) + { + this.collapsedSections = null; + this.height = height; + } + + internal double height; + internal List collapsedSections; + + internal bool IsDirectlyCollapsed { + get { return collapsedSections != null; } + } + + internal void AddDirectlyCollapsed(CollapsedLineSection section) + { + if (collapsedSections == null) + collapsedSections = new List(); + collapsedSections.Add(section); + } + + internal void RemoveDirectlyCollapsed(CollapsedLineSection section) + { + Debug.Assert(collapsedSections.Contains(section)); + collapsedSections.Remove(section); + if (collapsedSections.Count == 0) + collapsedSections = null; + } + + /// + /// Returns 0 if the line is directly collapsed, otherwise, returns . + /// + internal double TotalHeight { + get { + return IsDirectlyCollapsed ? 0 : height; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeNode.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeNode.cs new file mode 100644 index 000000000..42661edeb --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/HeightTreeNode.cs @@ -0,0 +1,155 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// A node in the text view's height tree. + /// + sealed class HeightTreeNode + { + internal readonly DocumentLine documentLine; + internal HeightTreeLineNode lineNode; + + internal HeightTreeNode left, right, parent; + internal bool color; + + internal HeightTreeNode() + { + } + + internal HeightTreeNode(DocumentLine documentLine, double height) + { + this.documentLine = documentLine; + this.totalCount = 1; + this.lineNode = new HeightTreeLineNode(height); + this.totalHeight = height; + } + + internal HeightTreeNode LeftMost { + get { + HeightTreeNode node = this; + while (node.left != null) + node = node.left; + return node; + } + } + + internal HeightTreeNode RightMost { + get { + HeightTreeNode node = this; + while (node.right != null) + node = node.right; + return node; + } + } + + /// + /// Gets the inorder successor of the node. + /// + internal HeightTreeNode Successor { + get { + if (right != null) { + return right.LeftMost; + } else { + HeightTreeNode node = this; + HeightTreeNode oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a left subtree + } while (node != null && node.right == oldNode); + return node; + } + } + } + + /// + /// The number of lines in this node and its child nodes. + /// Invariant: + /// totalCount = 1 + left.totalCount + right.totalCount + /// + internal int totalCount; + + /// + /// The total height of this node and its child nodes, excluding directly collapsed nodes. + /// Invariant: + /// totalHeight = left.IsDirectlyCollapsed ? 0 : left.totalHeight + /// + lineNode.IsDirectlyCollapsed ? 0 : lineNode.Height + /// + right.IsDirectlyCollapsed ? 0 : right.totalHeight + /// + internal double totalHeight; + + /// + /// List of the sections that hold this node collapsed. + /// Invariant 1: + /// For each document line in the range described by a CollapsedSection, exactly one ancestor + /// contains that CollapsedSection. + /// Invariant 2: + /// A CollapsedSection is contained either in left+middle or middle+right or just middle. + /// Invariant 3: + /// Start and end of a CollapsedSection always contain the collapsedSection in their + /// documentLine (middle node). + /// + internal List collapsedSections; + + internal bool IsDirectlyCollapsed { + get { + return collapsedSections != null; + } + } + + internal void AddDirectlyCollapsed(CollapsedLineSection section) + { + if (collapsedSections == null) { + collapsedSections = new List(); + totalHeight = 0; + } + Debug.Assert(!collapsedSections.Contains(section)); + collapsedSections.Add(section); + } + + + internal void RemoveDirectlyCollapsed(CollapsedLineSection section) + { + Debug.Assert(collapsedSections.Contains(section)); + collapsedSections.Remove(section); + if (collapsedSections.Count == 0) { + collapsedSections = null; + totalHeight = lineNode.TotalHeight; + if (left != null) + totalHeight += left.totalHeight; + if (right != null) + totalHeight += right.totalHeight; + } + } + + #if DEBUG + public override string ToString() + { + return "[HeightTreeNode " + + documentLine.LineNumber + " CS=" + GetCollapsedSections(collapsedSections) + + " Line.CS=" + GetCollapsedSections(lineNode.collapsedSections) + + " Line.Height=" + lineNode.height + + " TotalHeight=" + totalHeight + + "]"; + } + + static string GetCollapsedSections(List list) + { + if (list == null) + return "{}"; + return "{" + + string.Join(",", + list.ConvertAll(cs=>cs.ID).ToArray()) + + "}"; + } + #endif + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IBackgroundRenderer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IBackgroundRenderer.cs new file mode 100644 index 000000000..e16941147 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IBackgroundRenderer.cs @@ -0,0 +1,26 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Background renderers draw in the background of a known layer. + /// You can use background renderers to draw non-interactive elements on the TextView + /// without introducing new UIElements. + /// + public interface IBackgroundRenderer + { + /// + /// Gets the layer on which this background renderer should draw. + /// + KnownLayer Layer { get; } + + /// + /// Causes the background renderer to draw. + /// + void Draw(TextView textView, DrawingContext drawingContext); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextRunConstructionContext.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextRunConstructionContext.cs new file mode 100644 index 000000000..94c319bbf --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextRunConstructionContext.cs @@ -0,0 +1,47 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Contains information relevant for text run creation. + /// + public interface ITextRunConstructionContext + { + /// + /// Gets the text document. + /// + TextDocument Document { get; } + + /// + /// Gets the text view for which the construction runs. + /// + TextView TextView { get; } + + /// + /// Gets the visual line that is currently being constructed. + /// + VisualLine VisualLine { get; } + + /// + /// Gets the global text run properties. + /// + TextRunProperties GlobalTextRunProperties { get; } + + /// + /// Gets a piece of text from the document. + /// + /// + /// This method is allowed to return a larger string than requested. + /// It does this by returning a that describes the requested segment within the returned string. + /// This method should be the preferred text access method in the text transformation pipeline, as it can avoid repeatedly allocating string instances + /// for text within the same line. + /// + StringSegment GetText(int offset, int length); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextViewConnect.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextViewConnect.cs new file mode 100644 index 000000000..55a5c000c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/ITextViewConnect.cs @@ -0,0 +1,24 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Allows s, s and + /// s to be notified when they are added or removed from a text view. + /// + public interface ITextViewConnect + { + /// + /// Called when added to a text view. + /// + void AddToTextView(TextView textView); + + /// + /// Called when removed from a text view. + /// + void RemoveFromTextView(TextView textView); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IVisualLineTransformer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IVisualLineTransformer.cs new file mode 100644 index 000000000..91c2b6863 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/IVisualLineTransformer.cs @@ -0,0 +1,19 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Allows transforming visual line elements. + /// + public interface IVisualLineTransformer + { + /// + /// Applies the transformation to the specified list of visual line elements. + /// + void Transform(ITextRunConstructionContext context, IList elements); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/InlineObjectRun.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/InlineObjectRun.cs new file mode 100644 index 000000000..7448a8007 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/InlineObjectRun.cs @@ -0,0 +1,145 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// A inline UIElement in the document. + /// + public class InlineObjectElement : VisualLineElement + { + /// + /// Gets the inline element that is displayed. + /// + public UIElement Element { get; private set; } + + /// + /// Creates a new InlineObjectElement. + /// + /// The length of the element in the document. Must be non-negative. + /// The element to display. + public InlineObjectElement(int documentLength, UIElement element) + : base(1, documentLength) + { + if (element == null) + throw new ArgumentNullException("element"); + this.Element = element; + } + + /// + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + + return new InlineObjectRun(1, this.TextRunProperties, this.Element); + } + } + + /// + /// A text run with an embedded UIElement. + /// + public class InlineObjectRun : TextEmbeddedObject + { + UIElement element; + int length; + TextRunProperties properties; + internal Size desiredSize; + + /// + /// Creates a new InlineObjectRun instance. + /// + /// The length of the TextRun. + /// The to use. + /// The to display. + public InlineObjectRun(int length, TextRunProperties properties, UIElement element) + { + if (length <= 0) + throw new ArgumentOutOfRangeException("length", length, "Value must be positive"); + if (properties == null) + throw new ArgumentNullException("properties"); + if (element == null) + throw new ArgumentNullException("element"); + + this.length = length; + this.properties = properties; + this.element = element; + } + + /// + /// Gets the element displayed by the InlineObjectRun. + /// + public UIElement Element { + get { return element; } + } + + /// + /// Gets the VisualLine that contains this object. This property is only available after the object + /// was added to the text view. + /// + public VisualLine VisualLine { get; internal set; } + + /// + public override LineBreakCondition BreakBefore { + get { return LineBreakCondition.BreakDesired; } + } + + /// + public override LineBreakCondition BreakAfter { + get { return LineBreakCondition.BreakDesired; } + } + + /// + public override bool HasFixedSize { + get { return true; } + } + + /// + public override CharacterBufferReference CharacterBufferReference { + get { return new CharacterBufferReference(); } + } + + /// + public override int Length { + get { return length; } + } + + /// + public override TextRunProperties Properties { + get { return properties; } + } + + /// + public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth) + { + double baseline = TextBlock.GetBaselineOffset(element); + if (double.IsNaN(baseline)) + baseline = desiredSize.Height; + return new TextEmbeddedObjectMetrics(desiredSize.Width, desiredSize.Height, baseline); + } + + /// + public override Rect ComputeBoundingBox(bool rightToLeft, bool sideways) + { + if (this.element.IsArrangeValid) { + double baseline = TextBlock.GetBaselineOffset(element); + if (double.IsNaN(baseline)) + baseline = desiredSize.Height; + return new Rect(new Point(0, -baseline), desiredSize); + } else { + return Rect.Empty; + } + } + + /// + public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/Layer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/Layer.cs new file mode 100644 index 000000000..0d4777813 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/Layer.cs @@ -0,0 +1,43 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Base class for known layers. + /// + class Layer : UIElement + { + protected readonly TextView textView; + protected readonly KnownLayer knownLayer; + + public Layer(TextView textView, KnownLayer knownLayer) + { + Debug.Assert(textView != null); + this.textView = textView; + this.knownLayer = knownLayer; + this.Focusable = false; + } + + protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters) + { + return null; + } + + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + return null; + } + + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + textView.RenderBackground(drawingContext, knownLayer); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LayerPosition.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LayerPosition.cs new file mode 100644 index 000000000..f64923eea --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LayerPosition.cs @@ -0,0 +1,91 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// An enumeration of well-known layers. + /// + public enum KnownLayer + { + /// + /// This layer is in the background. + /// There is no UIElement to represent this layer, it is directly drawn in the TextView. + /// It is not possible to replace the background layer or insert new layers below it. + /// + /// This layer is below the Selection layer. + Background, + /// + /// This layer contains the selection rectangle. + /// + /// This layer is between the Background and the Text layers. + Selection, + /// + /// This layer contains the text and inline UI elements. + /// + /// This layer is between the Selection and the Caret layers. + Text, + /// + /// This layer contains the blinking caret. + /// + /// This layer is above the Text layer. + Caret + } + + /// + /// Specifies where a new layer is inserted, in relation to an old layer. + /// + public enum LayerInsertionPosition + { + /// + /// The new layer is inserted below the specified layer. + /// + Below, + /// + /// The new layer replaces the specified layer. The old layer is removed + /// from the collection. + /// + Replace, + /// + /// The new layer is inserted above the specified layer. + /// + Above + } + + sealed class LayerPosition : IComparable + { + internal static readonly DependencyProperty LayerPositionProperty = + DependencyProperty.RegisterAttached("LayerPosition", typeof(LayerPosition), typeof(LayerPosition)); + + public static void SetLayerPosition(UIElement layer, LayerPosition value) + { + layer.SetValue(LayerPositionProperty, value); + } + + public static LayerPosition GetLayerPosition(UIElement layer) + { + return (LayerPosition)layer.GetValue(LayerPositionProperty); + } + + internal readonly KnownLayer KnownLayer; + internal readonly LayerInsertionPosition Position; + + public LayerPosition(KnownLayer knownLayer, LayerInsertionPosition position) + { + this.KnownLayer = knownLayer; + this.Position = position; + } + + public int CompareTo(LayerPosition other) + { + int r = this.KnownLayer.CompareTo(other.KnownLayer); + if (r != 0) + return r; + else + return this.Position.CompareTo(other.Position); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LinkElementGenerator.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LinkElementGenerator.cs new file mode 100644 index 000000000..a5cb81c12 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/LinkElementGenerator.cs @@ -0,0 +1,144 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text.RegularExpressions; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + // This class is public because it can be used as a base class for custom links. + + /// + /// Detects hyperlinks and makes them clickable. + /// + /// + /// This element generator can be easily enabled and configured using the + /// . + /// + public class LinkElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator + { + // a link starts with a protocol (or just with www), followed by 0 or more 'link characters', followed by a link end character + // (this allows accepting punctuation inside links but not at the end) + internal readonly static Regex defaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]"); + + // try to detect email addresses + internal readonly static Regex defaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b"); + + readonly Regex linkRegex; + + /// + /// Gets/Sets whether the user needs to press Control to click the link. + /// The default value is true. + /// + public bool RequireControlModifierForClick { get; set; } + + /// + /// Creates a new LinkElementGenerator. + /// + public LinkElementGenerator() + { + this.linkRegex = defaultLinkRegex; + this.RequireControlModifierForClick = true; + } + + /// + /// Creates a new LinkElementGenerator using the specified regex. + /// + protected LinkElementGenerator(Regex regex) : this() + { + if (regex == null) + throw new ArgumentNullException("regex"); + this.linkRegex = regex; + } + + void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) + { + this.RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick; + } + + Match GetMatch(int startOffset, out int matchOffset) + { + int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; + StringSegment relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset); + Match m = linkRegex.Match(relevantText.Text, relevantText.Offset, relevantText.Count); + matchOffset = m.Success ? m.Index - relevantText.Offset + startOffset : -1; + return m; + } + + /// + public override int GetFirstInterestedOffset(int startOffset) + { + int matchOffset; + GetMatch(startOffset, out matchOffset); + return matchOffset; + } + + /// + public override VisualLineElement ConstructElement(int offset) + { + int matchOffset; + Match m = GetMatch(offset, out matchOffset); + if (m.Success && matchOffset == offset) { + return ConstructElementFromMatch(m); + } else { + return null; + } + } + + /// + /// Constructs a VisualLineElement that replaces the matched text. + /// The default implementation will create a + /// based on the URI provided by . + /// + protected virtual VisualLineElement ConstructElementFromMatch(Match m) + { + Uri uri = GetUriFromMatch(m); + if (uri == null) + return null; + VisualLineLinkText linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length); + linkText.NavigateUri = uri; + linkText.RequireControlModifierForClick = this.RequireControlModifierForClick; + return linkText; + } + + /// + /// Fetches the URI from the regex match. Returns null if the URI format is invalid. + /// + protected virtual Uri GetUriFromMatch(Match match) + { + string targetUrl = match.Value; + if (targetUrl.StartsWith("www.", StringComparison.Ordinal)) + targetUrl = "http://" + targetUrl; + if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute)) + return new Uri(targetUrl); + + return null; + } + } + + // This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions. + + /// + /// Detects e-mail addresses and makes them clickable. + /// + /// + /// This element generator can be easily enabled and configured using the + /// . + /// + sealed class MailLinkElementGenerator : LinkElementGenerator + { + /// + /// Creates a new MailLinkElementGenerator. + /// + public MailLinkElementGenerator() + : base(defaultMailRegex) + { + } + + protected override Uri GetUriFromMatch(Match match) + { + return new Uri("mailto:" + match.Value); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/MouseHoverLogic.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/MouseHoverLogic.cs new file mode 100644 index 000000000..857affcbb --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/MouseHoverLogic.cs @@ -0,0 +1,134 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Encapsulates and adds MouseHover support to UIElements. + /// + public class MouseHoverLogic : IDisposable + { + UIElement target; + + DispatcherTimer mouseHoverTimer; + Point mouseHoverStartPoint; + MouseEventArgs mouseHoverLastEventArgs; + bool mouseHovering; + + /// + /// Creates a new instance and attaches itself to the UIElement. + /// + public MouseHoverLogic(UIElement target) + { + if (target == null) + throw new ArgumentNullException("target"); + this.target = target; + this.target.MouseLeave += MouseHoverLogicMouseLeave; + this.target.MouseMove += MouseHoverLogicMouseMove; + this.target.MouseEnter += MouseHoverLogicMouseEnter; + } + + void MouseHoverLogicMouseMove(object sender, MouseEventArgs e) + { + Vector mouseMovement = mouseHoverStartPoint - e.GetPosition(this.target); + if (Math.Abs(mouseMovement.X) > SystemParameters.MouseHoverWidth + || Math.Abs(mouseMovement.Y) > SystemParameters.MouseHoverHeight) + { + StartHovering(e); + } + // do not set e.Handled - allow others to also handle MouseMove + } + + void MouseHoverLogicMouseEnter(object sender, MouseEventArgs e) + { + StartHovering(e); + // do not set e.Handled - allow others to also handle MouseEnter + } + + void StartHovering(MouseEventArgs e) + { + StopHovering(); + mouseHoverStartPoint = e.GetPosition(this.target); + mouseHoverLastEventArgs = e; + mouseHoverTimer = new DispatcherTimer(SystemParameters.MouseHoverTime, DispatcherPriority.Background, OnMouseHoverTimerElapsed, this.target.Dispatcher); + mouseHoverTimer.Start(); + } + + void MouseHoverLogicMouseLeave(object sender, MouseEventArgs e) + { + StopHovering(); + // do not set e.Handled - allow others to also handle MouseLeave + } + + void StopHovering() + { + if (mouseHoverTimer != null) { + mouseHoverTimer.Stop(); + mouseHoverTimer = null; + } + if (mouseHovering) { + mouseHovering = false; + OnMouseHoverStopped(mouseHoverLastEventArgs); + } + } + + void OnMouseHoverTimerElapsed(object sender, EventArgs e) + { + mouseHoverTimer.Stop(); + mouseHoverTimer = null; + + mouseHovering = true; + OnMouseHover(mouseHoverLastEventArgs); + } + + /// + /// Occurs when the mouse starts hovering over a certain location. + /// + public event EventHandler MouseHover; + + /// + /// Raises the event. + /// + protected virtual void OnMouseHover(MouseEventArgs e) + { + if (MouseHover != null) { + MouseHover(this, e); + } + } + + /// + /// Occurs when the mouse stops hovering over a certain location. + /// + public event EventHandler MouseHoverStopped; + + /// + /// Raises the event. + /// + protected virtual void OnMouseHoverStopped(MouseEventArgs e) + { + if (MouseHoverStopped != null) { + MouseHoverStopped(this, e); + } + } + + bool disposed; + + /// + /// Removes the MouseHover support from the target UIElement. + /// + public void Dispose() + { + if (!disposed) { + this.target.MouseLeave -= MouseHoverLogicMouseLeave; + this.target.MouseMove -= MouseHoverLogicMouseMove; + this.target.MouseEnter -= MouseHoverLogicMouseEnter; + } + disposed = true; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SimpleTextSource.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SimpleTextSource.cs new file mode 100644 index 000000000..8e9ac568b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SimpleTextSource.cs @@ -0,0 +1,39 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows.Media.TextFormatting; + +namespace Tango.Scripting.Editors.Rendering +{ + sealed class SimpleTextSource : TextSource + { + readonly string text; + readonly TextRunProperties properties; + + public SimpleTextSource(string text, TextRunProperties properties) + { + this.text = text; + this.properties = properties; + } + + public override TextRun GetTextRun(int textSourceCharacterIndex) + { + if (textSourceCharacterIndex < text.Length) + return new TextCharacters(text, textSourceCharacterIndex, text.Length - textSourceCharacterIndex, properties); + else + return new TextEndOfParagraph(1); + } + + public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int textSourceCharacterIndex) + { + throw new NotImplementedException(); + } + + public override TextSpan GetPrecedingText(int textSourceCharacterIndexLimit) + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SingleCharacterElementGenerator.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SingleCharacterElementGenerator.cs new file mode 100644 index 000000000..53c4aac81 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/SingleCharacterElementGenerator.cs @@ -0,0 +1,268 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + // This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions. + + /// + /// Element generator that displays · for spaces and » for tabs and a box for control characters. + /// + /// + /// This element generator is present in every TextView by default; the enabled features can be configured using the + /// . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace")] + sealed class SingleCharacterElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator + { + /// + /// Gets/Sets whether to show · for spaces. + /// + public bool ShowSpaces { get; set; } + + /// + /// Gets/Sets whether to show » for tabs. + /// + public bool ShowTabs { get; set; } + + /// + /// Gets/Sets whether to show a box with the hex code for control characters. + /// + public bool ShowBoxForControlCharacters { get; set; } + + /// + /// Creates a new SingleCharacterElementGenerator instance. + /// + public SingleCharacterElementGenerator() + { + this.ShowSpaces = true; + this.ShowTabs = true; + this.ShowBoxForControlCharacters = true; + } + + void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) + { + this.ShowSpaces = options.ShowSpaces; + this.ShowTabs = options.ShowTabs; + this.ShowBoxForControlCharacters = options.ShowBoxForControlCharacters; + } + + public override int GetFirstInterestedOffset(int startOffset) + { + DocumentLine endLine = CurrentContext.VisualLine.LastDocumentLine; + StringSegment relevantText = CurrentContext.GetText(startOffset, endLine.EndOffset - startOffset); + + for (int i = 0; i < relevantText.Count; i++) { + char c = relevantText.Text[relevantText.Offset + i]; + switch (c) { + case ' ': + if (ShowSpaces) + return startOffset + i; + break; + case '\t': + if (ShowTabs) + return startOffset + i; + break; + default: + if (ShowBoxForControlCharacters && char.IsControl(c)) { + return startOffset + i; + } + break; + } + } + return -1; + } + + public override VisualLineElement ConstructElement(int offset) + { + char c = CurrentContext.Document.GetCharAt(offset); + if (ShowSpaces && c == ' ') { + return new SpaceTextElement(CurrentContext.TextView.cachedElements.GetTextForNonPrintableCharacter("\u00B7", CurrentContext)); + } else if (ShowTabs && c == '\t') { + return new TabTextElement(CurrentContext.TextView.cachedElements.GetTextForNonPrintableCharacter("\u00BB", CurrentContext)); + } else if (ShowBoxForControlCharacters && char.IsControl(c)) { + var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties); + p.SetForegroundBrush(Brushes.White); + var textFormatter = TextFormatterFactory.Create(CurrentContext.TextView); + var text = FormattedTextElement.PrepareText(textFormatter, + TextUtilities.GetControlCharacterName(c), p); + return new SpecialCharacterBoxElement(text); + } else { + return null; + } + } + + sealed class SpaceTextElement : FormattedTextElement + { + public SpaceTextElement(TextLine textLine) : base(textLine, 1) + { + BreakBefore = LineBreakCondition.BreakPossible; + BreakAfter = LineBreakCondition.BreakDesired; + } + + public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) + { + if (mode == CaretPositioningMode.Normal) + return base.GetNextCaretPosition(visualColumn, direction, mode); + else + return -1; + } + + public override bool IsWhitespace(int visualColumn) + { + return true; + } + } + + sealed class TabTextElement : VisualLineElement + { + internal readonly TextLine text; + + public TabTextElement(TextLine text) : base(2, 1) + { + this.text = text; + } + + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + // the TabTextElement consists of two TextRuns: + // first a TabGlyphRun, then TextCharacters '\t' to let WPF handle the tab indentation + if (startVisualColumn == this.VisualColumn) + return new TabGlyphRun(this, this.TextRunProperties); + else if (startVisualColumn == this.VisualColumn + 1) + return new TextCharacters("\t", 0, 1, this.TextRunProperties); + else + throw new ArgumentOutOfRangeException("startVisualColumn"); + } + + public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) + { + if (mode == CaretPositioningMode.Normal) + return base.GetNextCaretPosition(visualColumn, direction, mode); + else + return -1; + } + + public override bool IsWhitespace(int visualColumn) + { + return true; + } + } + + sealed class TabGlyphRun : TextEmbeddedObject + { + readonly TabTextElement element; + TextRunProperties properties; + + public TabGlyphRun(TabTextElement element, TextRunProperties properties) + { + if (properties == null) + throw new ArgumentNullException("properties"); + this.properties = properties; + this.element = element; + } + + public override LineBreakCondition BreakBefore { + get { return LineBreakCondition.BreakPossible; } + } + + public override LineBreakCondition BreakAfter { + get { return LineBreakCondition.BreakRestrained; } + } + + public override bool HasFixedSize { + get { return true; } + } + + public override CharacterBufferReference CharacterBufferReference { + get { return new CharacterBufferReference(); } + } + + public override int Length { + get { return 1; } + } + + public override TextRunProperties Properties { + get { return properties; } + } + + public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth) + { + double width = Math.Min(0, element.text.WidthIncludingTrailingWhitespace - 1); + return new TextEmbeddedObjectMetrics(width, element.text.Height, element.text.Baseline); + } + + public override Rect ComputeBoundingBox(bool rightToLeft, bool sideways) + { + double width = Math.Min(0, element.text.WidthIncludingTrailingWhitespace - 1); + return new Rect(0, 0, width, element.text.Height); + } + + public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways) + { + origin.Y -= element.text.Baseline; + element.text.Draw(drawingContext, origin, InvertAxes.None); + } + } + + sealed class SpecialCharacterBoxElement : FormattedTextElement + { + public SpecialCharacterBoxElement(TextLine text) : base(text, 1) + { + } + + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + return new SpecialCharacterTextRun(this, this.TextRunProperties); + } + } + + sealed class SpecialCharacterTextRun : FormattedTextRun + { + static readonly SolidColorBrush darkGrayBrush; + + static SpecialCharacterTextRun() + { + darkGrayBrush = new SolidColorBrush(Color.FromArgb(200, 128, 128, 128)); + darkGrayBrush.Freeze(); + } + + public SpecialCharacterTextRun(FormattedTextElement element, TextRunProperties properties) + : base(element, properties) + { + } + + public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways) + { + Point newOrigin = new Point(origin.X + 1.5, origin.Y); + var metrics = base.Format(double.PositiveInfinity); + Rect r = new Rect(newOrigin.X - 0.5, newOrigin.Y - metrics.Baseline, metrics.Width + 2, metrics.Height); + drawingContext.DrawRoundedRectangle(darkGrayBrush, null, r, 2.5, 2.5); + base.Draw(drawingContext, newOrigin, rightToLeft, sideways); + } + + public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth) + { + TextEmbeddedObjectMetrics metrics = base.Format(remainingParagraphWidth); + return new TextEmbeddedObjectMetrics(metrics.Width + 3, + metrics.Height, metrics.Baseline); + } + + public override Rect ComputeBoundingBox(bool rightToLeft, bool sideways) + { + Rect r = base.ComputeBoundingBox(rightToLeft, sideways); + r.Width += 3; + return r; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextLayer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextLayer.cs new file mode 100644 index 000000000..3f4b8299b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextLayer.cs @@ -0,0 +1,70 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// The control that contains the text. + /// + /// This control is used to allow other UIElements to be placed inside the TextView but + /// behind the text. + /// The text rendering process (VisualLine creation) is controlled by the TextView, this + /// class simply displays the created Visual Lines. + /// + /// + /// This class does not contain any input handling and is invisible to hit testing. Input + /// is handled by the TextView. + /// This allows UIElements that are displayed behind the text, but still can react to mouse input. + /// + sealed class TextLayer : Layer + { + /// + /// the index of the text layer in the layers collection + /// + internal int index; + + public TextLayer(TextView textView) : base(textView, KnownLayer.Text) + { + } + + List visuals = new List(); + + internal void SetVisualLines(ICollection visualLines) + { + foreach (VisualLineDrawingVisual v in visuals) { + if (v.VisualLine.IsDisposed) + RemoveVisualChild(v); + } + visuals.Clear(); + foreach (VisualLine newLine in visualLines) { + VisualLineDrawingVisual v = newLine.Render(); + if (!v.IsAdded) { + AddVisualChild(v); + v.IsAdded = true; + } + visuals.Add(v); + } + InvalidateArrange(); + } + + protected override int VisualChildrenCount { + get { return visuals.Count; } + } + + protected override Visual GetVisualChild(int index) + { + return visuals[index]; + } + + protected override void ArrangeCore(Rect finalRect) + { + textView.ArrangeTextLayer(visuals); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextView.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextView.cs new file mode 100644 index 000000000..3dabb6b7a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextView.cs @@ -0,0 +1,2009 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// A virtualizing panel producing+showing s for a . + /// + /// This is the heart of the text editor, this class controls the text rendering process. + /// + /// Taken as a standalone control, it's a text viewer without any editing capability. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", + Justification = "The user usually doesn't work with TextView but with TextEditor; and nulling the Document property is sufficient to dispose everything.")] + public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider + { + #region Constructor + static TextView() + { + ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.True)); + FocusableProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.False)); + } + + ColumnRulerRenderer columnRulerRenderer; + + /// + /// Creates a new TextView instance. + /// + public TextView() + { + services.AddService(typeof(TextView), this); + textLayer = new TextLayer(this); + elementGenerators = new ObserveAddRemoveCollection(ElementGenerator_Added, ElementGenerator_Removed); + lineTransformers = new ObserveAddRemoveCollection(LineTransformer_Added, LineTransformer_Removed); + backgroundRenderers = new ObserveAddRemoveCollection(BackgroundRenderer_Added, BackgroundRenderer_Removed); + columnRulerRenderer = new ColumnRulerRenderer(this); + this.Options = new TextEditorOptions(); + + Debug.Assert(singleCharacterElementGenerator != null); // assert that the option change created the builtin element generators + + layers = new LayerCollection(this); + InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace); + + this.hoverLogic = new MouseHoverLogic(this); + this.hoverLogic.MouseHover += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverEvent, MouseHoverEvent); + this.hoverLogic.MouseHoverStopped += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent); + } + + #endregion + + #region Document Property + /// + /// Document property. + /// + public static readonly DependencyProperty DocumentProperty = + DependencyProperty.Register("Document", typeof(TextDocument), typeof(TextView), + new FrameworkPropertyMetadata(OnDocumentChanged)); + + TextDocument document; + HeightTree heightTree; + + /// + /// Gets/Sets the document displayed by the text editor. + /// + public TextDocument Document { + get { return (TextDocument)GetValue(DocumentProperty); } + set { SetValue(DocumentProperty, value); } + } + + static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextView)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue); + } + + internal double FontSize { + get { + return (double)GetValue(TextBlock.FontSizeProperty); + } + } + + /// + /// Occurs when the document property has changed. + /// + public event EventHandler DocumentChanged; + + void OnDocumentChanged(TextDocument oldValue, TextDocument newValue) + { + if (oldValue != null) { + heightTree.Dispose(); + heightTree = null; + formatter.Dispose(); + formatter = null; + cachedElements.Dispose(); + cachedElements = null; + TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this); + } + this.document = newValue; + ClearScrollData(); + ClearVisualLines(); + if (newValue != null) { + TextDocumentWeakEventManager.Changing.AddListener(newValue, this); + formatter = TextFormatterFactory.Create(this); + InvalidateDefaultTextMetrics(); // measuring DefaultLineHeight depends on formatter + heightTree = new HeightTree(newValue, DefaultLineHeight); + cachedElements = new TextViewCachedElements(); + } + InvalidateMeasure(DispatcherPriority.Normal); + if (DocumentChanged != null) + DocumentChanged(this, EventArgs.Empty); + } + + /// + /// Recreates the text formatter that is used internally + /// by calling . + /// + void RecreateTextFormatter() + { + if (formatter != null) { + formatter.Dispose(); + formatter = TextFormatterFactory.Create(this); + Redraw(); + } + } + + void RecreateCachedElements() + { + if (cachedElements != null) { + cachedElements.Dispose(); + cachedElements = new TextViewCachedElements(); + } + } + + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextDocumentWeakEventManager.Changing)) { + // TODO: put redraw into background so that other input events can be handled before the redraw. + // Unfortunately the "easy" approach (just use DispatcherPriority.Background) here makes the editor twice as slow because + // the caret position change forces an immediate redraw, and the text input then forces a background redraw. + // When fixing this, make sure performance on the SharpDevelop "type text in C# comment" stress test doesn't get significantly worse. + DocumentChangeEventArgs change = (DocumentChangeEventArgs)e; + Redraw(change.Offset, change.RemovalLength, DispatcherPriority.Normal); + return true; + } else if (managerType == typeof(PropertyChangedWeakEventManager)) { + OnOptionChanged((PropertyChangedEventArgs)e); + return true; + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + #endregion + + #region Options property + /// + /// Options property. + /// + public static readonly DependencyProperty OptionsProperty = + DependencyProperty.Register("Options", typeof(TextEditorOptions), typeof(TextView), + new FrameworkPropertyMetadata(OnOptionsChanged)); + + /// + /// Gets/Sets the options used by the text editor. + /// + public TextEditorOptions Options { + get { return (TextEditorOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } + + /// + /// Occurs when a text editor option has changed. + /// + public event PropertyChangedEventHandler OptionChanged; + + /// + /// Raises the event. + /// + protected virtual void OnOptionChanged(PropertyChangedEventArgs e) + { + if (OptionChanged != null) { + OptionChanged(this, e); + } + + if (Options.ShowColumnRuler) + columnRulerRenderer.SetRuler(Options.ColumnRulerPosition, ColumnRulerPen); + else + columnRulerRenderer.SetRuler(-1, ColumnRulerPen); + + UpdateBuiltinElementGeneratorsFromOptions(); + Redraw(); + } + + static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextView)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue); + } + + void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue) + { + if (oldValue != null) { + PropertyChangedWeakEventManager.RemoveListener(oldValue, this); + } + if (newValue != null) { + PropertyChangedWeakEventManager.AddListener(newValue, this); + } + OnOptionChanged(new PropertyChangedEventArgs(null)); + } + #endregion + + #region ElementGenerators+LineTransformers Properties + readonly ObserveAddRemoveCollection elementGenerators; + + /// + /// Gets a collection where element generators can be registered. + /// + public IList ElementGenerators { + get { return elementGenerators; } + } + + void ElementGenerator_Added(VisualLineElementGenerator generator) + { + ConnectToTextView(generator); + Redraw(); + } + + void ElementGenerator_Removed(VisualLineElementGenerator generator) + { + DisconnectFromTextView(generator); + Redraw(); + } + + readonly ObserveAddRemoveCollection lineTransformers; + + /// + /// Gets a collection where line transformers can be registered. + /// + public IList LineTransformers { + get { return lineTransformers; } + } + + void LineTransformer_Added(IVisualLineTransformer lineTransformer) + { + ConnectToTextView(lineTransformer); + Redraw(); + } + + void LineTransformer_Removed(IVisualLineTransformer lineTransformer) + { + DisconnectFromTextView(lineTransformer); + Redraw(); + } + #endregion + + #region Builtin ElementGenerators +// NewLineElementGenerator newLineElementGenerator; + SingleCharacterElementGenerator singleCharacterElementGenerator; + LinkElementGenerator linkElementGenerator; + MailLinkElementGenerator mailLinkElementGenerator; + + void UpdateBuiltinElementGeneratorsFromOptions() + { + TextEditorOptions options = this.Options; + +// AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine); + AddRemoveDefaultElementGeneratorOnDemand(ref singleCharacterElementGenerator, options.ShowBoxForControlCharacters || options.ShowSpaces || options.ShowTabs); + AddRemoveDefaultElementGeneratorOnDemand(ref linkElementGenerator, options.EnableHyperlinks); + AddRemoveDefaultElementGeneratorOnDemand(ref mailLinkElementGenerator, options.EnableEmailHyperlinks); + } + + void AddRemoveDefaultElementGeneratorOnDemand(ref T generator, bool demand) + where T : VisualLineElementGenerator, IBuiltinElementGenerator, new() + { + bool hasGenerator = generator != null; + if (hasGenerator != demand) { + if (demand) { + generator = new T(); + this.ElementGenerators.Add(generator); + } else { + this.ElementGenerators.Remove(generator); + generator = null; + } + } + if (generator != null) + generator.FetchOptions(this.Options); + } + #endregion + + #region Layers + internal readonly TextLayer textLayer; + readonly LayerCollection layers; + + /// + /// Gets the list of layers displayed in the text view. + /// + public UIElementCollection Layers { + get { return layers; } + } + + sealed class LayerCollection : UIElementCollection + { + readonly TextView textView; + + public LayerCollection(TextView textView) + : base(textView, textView) + { + this.textView = textView; + } + + public override void Clear() + { + base.Clear(); + textView.LayersChanged(); + } + + public override int Add(UIElement element) + { + int r = base.Add(element); + textView.LayersChanged(); + return r; + } + + public override void RemoveAt(int index) + { + base.RemoveAt(index); + textView.LayersChanged(); + } + + public override void RemoveRange(int index, int count) + { + base.RemoveRange(index, count); + textView.LayersChanged(); + } + } + + void LayersChanged() + { + textLayer.index = layers.IndexOf(textLayer); + } + + /// + /// Inserts a new layer at a position specified relative to an existing layer. + /// + /// The new layer to insert. + /// The existing layer + /// Specifies whether the layer is inserted above,below, or replaces the referenced layer + public void InsertLayer(UIElement layer, KnownLayer referencedLayer, LayerInsertionPosition position) + { + if (layer == null) + throw new ArgumentNullException("layer"); + if (!Enum.IsDefined(typeof(KnownLayer), referencedLayer)) + throw new InvalidEnumArgumentException("referencedLayer", (int)referencedLayer, typeof(KnownLayer)); + if (!Enum.IsDefined(typeof(LayerInsertionPosition), position)) + throw new InvalidEnumArgumentException("position", (int)position, typeof(LayerInsertionPosition)); + if (referencedLayer == KnownLayer.Background && position != LayerInsertionPosition.Above) + throw new InvalidOperationException("Cannot replace or insert below the background layer."); + + LayerPosition newPosition = new LayerPosition(referencedLayer, position); + LayerPosition.SetLayerPosition(layer, newPosition); + for (int i = 0; i < layers.Count; i++) { + LayerPosition p = LayerPosition.GetLayerPosition(layers[i]); + if (p != null) { + if (p.KnownLayer == referencedLayer && p.Position == LayerInsertionPosition.Replace) { + // found the referenced layer + switch (position) { + case LayerInsertionPosition.Below: + layers.Insert(i, layer); + return; + case LayerInsertionPosition.Above: + layers.Insert(i + 1, layer); + return; + case LayerInsertionPosition.Replace: + layers[i] = layer; + return; + } + } else if (p.KnownLayer == referencedLayer && p.Position == LayerInsertionPosition.Above + || p.KnownLayer > referencedLayer) { + // we skipped the insertion position (referenced layer does not exist?) + layers.Insert(i, layer); + return; + } + } + } + // inserting after all existing layers: + layers.Add(layer); + } + + /// + protected override int VisualChildrenCount { + get { return layers.Count + inlineObjects.Count; } + } + + /// + protected override Visual GetVisualChild(int index) + { + int cut = textLayer.index + 1; + if (index < cut) + return layers[index]; + else if (index < cut + inlineObjects.Count) + return inlineObjects[index - cut].Element; + else + return layers[index - inlineObjects.Count]; + } + + /// + protected override System.Collections.IEnumerator LogicalChildren { + get { + return inlineObjects.Select(io => io.Element).Concat(layers.Cast()).GetEnumerator(); + } + } + #endregion + + #region Inline object handling + List inlineObjects = new List(); + + /// + /// Adds a new inline object. + /// + internal void AddInlineObject(InlineObjectRun inlineObject) + { + Debug.Assert(inlineObject.VisualLine != null); + + // Remove inline object if its already added, can happen e.g. when recreating textrun for word-wrapping + bool alreadyAdded = false; + for (int i = 0; i < inlineObjects.Count; i++) { + if (inlineObjects[i].Element == inlineObject.Element) { + RemoveInlineObjectRun(inlineObjects[i], true); + inlineObjects.RemoveAt(i); + alreadyAdded = true; + break; + } + } + + inlineObjects.Add(inlineObject); + if (!alreadyAdded) { + AddVisualChild(inlineObject.Element); + } + inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + inlineObject.desiredSize = inlineObject.Element.DesiredSize; + } + + void MeasureInlineObjects() + { + // As part of MeasureOverride(), re-measure the inline objects + foreach (InlineObjectRun inlineObject in inlineObjects) { + if (inlineObject.VisualLine.IsDisposed) { + // Don't re-measure inline objects that are going to be removed anyways. + // If the inline object will be reused in a different VisualLine, we'll measure it in the AddInlineObject() call. + continue; + } + inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + if (!inlineObject.Element.DesiredSize.IsClose(inlineObject.desiredSize)) { + // the element changed size -> recreate its parent visual line + inlineObject.desiredSize = inlineObject.Element.DesiredSize; + if (allVisualLines.Remove(inlineObject.VisualLine)) { + DisposeVisualLine(inlineObject.VisualLine); + } + } + } + } + + List visualLinesWithOutstandingInlineObjects = new List(); + + void RemoveInlineObjects(VisualLine visualLine) + { + // Delay removing inline objects: + // A document change immediately invalidates affected visual lines, but it does not + // cause an immediate redraw. + // To prevent inline objects from flickering when they are recreated, we delay removing + // inline objects until the next redraw. + if (visualLine.hasInlineObjects) { + visualLinesWithOutstandingInlineObjects.Add(visualLine); + } + } + + /// + /// Remove the inline objects that were marked for removal. + /// + void RemoveInlineObjectsNow() + { + if (visualLinesWithOutstandingInlineObjects.Count == 0) + return; + inlineObjects.RemoveAll( + ior => { + if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) { + RemoveInlineObjectRun(ior, false); + return true; + } + return false; + }); + visualLinesWithOutstandingInlineObjects.Clear(); + } + + // Remove InlineObjectRun.Element from TextLayer. + // Caller of RemoveInlineObjectRun will remove it from inlineObjects collection. + void RemoveInlineObjectRun(InlineObjectRun ior, bool keepElement) + { + if (!keepElement && ior.Element.IsKeyboardFocusWithin) { + // When the inline element that has the focus is removed, WPF will reset the + // focus to the main window without raising appropriate LostKeyboardFocus events. + // To work around this, we manually set focus to the next focusable parent. + UIElement element = this; + while (element != null && !element.Focusable) { + element = VisualTreeHelper.GetParent(element) as UIElement; + } + if (element != null) + Keyboard.Focus(element); + } + ior.VisualLine = null; + if (!keepElement) + RemoveVisualChild(ior.Element); + } + #endregion + + #region Brushes + /// + /// NonPrintableCharacterBrush dependency property. + /// + public static readonly DependencyProperty NonPrintableCharacterBrushProperty = + DependencyProperty.Register("NonPrintableCharacterBrush", typeof(Brush), typeof(TextView), + new FrameworkPropertyMetadata(Brushes.LightGray)); + + /// + /// Gets/sets the Brush used for displaying non-printable characters. + /// + public Brush NonPrintableCharacterBrush { + get { return (Brush)GetValue(NonPrintableCharacterBrushProperty); } + set { SetValue(NonPrintableCharacterBrushProperty, value); } + } + + /// + /// LinkTextForegroundBrush dependency property. + /// + public static readonly DependencyProperty LinkTextForegroundBrushProperty = + DependencyProperty.Register("LinkTextForegroundBrush", typeof(Brush), typeof(TextView), + new FrameworkPropertyMetadata(Brushes.Blue)); + + /// + /// Gets/sets the Brush used for displaying link texts. + /// + public Brush LinkTextForegroundBrush { + get { return (Brush)GetValue(LinkTextForegroundBrushProperty); } + set { SetValue(LinkTextForegroundBrushProperty, value); } + } + + /// + /// LinkTextBackgroundBrush dependency property. + /// + public static readonly DependencyProperty LinkTextBackgroundBrushProperty = + DependencyProperty.Register("LinkTextBackgroundBrush", typeof(Brush), typeof(TextView), + new FrameworkPropertyMetadata(Brushes.Transparent)); + + /// + /// Gets/sets the Brush used for the background of link texts. + /// + public Brush LinkTextBackgroundBrush { + get { return (Brush)GetValue(LinkTextBackgroundBrushProperty); } + set { SetValue(LinkTextBackgroundBrushProperty, value); } + } + #endregion + + #region Redraw methods / VisualLine invalidation + /// + /// Causes the text editor to regenerate all visual lines. + /// + public void Redraw() + { + Redraw(DispatcherPriority.Normal); + } + + /// + /// Causes the text editor to regenerate all visual lines. + /// + public void Redraw(DispatcherPriority redrawPriority) + { + VerifyAccess(); + ClearVisualLines(); + InvalidateMeasure(redrawPriority); + } + + /// + /// Causes the text editor to regenerate the specified visual line. + /// + public void Redraw(VisualLine visualLine, DispatcherPriority redrawPriority = DispatcherPriority.Normal) + { + VerifyAccess(); + if (allVisualLines.Remove(visualLine)) { + DisposeVisualLine(visualLine); + InvalidateMeasure(redrawPriority); + } + } + + /// + /// Causes the text editor to redraw all lines overlapping with the specified segment. + /// + public void Redraw(int offset, int length, DispatcherPriority redrawPriority = DispatcherPriority.Normal) + { + VerifyAccess(); + bool changedSomethingBeforeOrInLine = false; + for (int i = 0; i < allVisualLines.Count; i++) { + VisualLine visualLine = allVisualLines[i]; + int lineStart = visualLine.FirstDocumentLine.Offset; + int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength; + if (offset <= lineEnd) { + changedSomethingBeforeOrInLine = true; + if (offset + length >= lineStart) { + allVisualLines.RemoveAt(i--); + DisposeVisualLine(visualLine); + } + } + } + if (changedSomethingBeforeOrInLine) { + // Repaint not only when something in visible area was changed, but also when anything in front of it + // was changed. We might have to redraw the line number margin. Or the highlighting changed. + // However, we'll try to reuse the existing VisualLines. + InvalidateMeasure(redrawPriority); + } + } + + /// + /// Causes a known layer to redraw. + /// This method does not invalidate visual lines; + /// use the method to do that. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "knownLayer", + Justification="This method is meant to invalidate only a specific layer - I just haven't figured out how to do that, yet.")] + public void InvalidateLayer(KnownLayer knownLayer) + { + InvalidateMeasure(DispatcherPriority.Normal); + } + + /// + /// Causes a known layer to redraw. + /// This method does not invalidate visual lines; + /// use the method to do that. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "knownLayer", + Justification="This method is meant to invalidate only a specific layer - I just haven't figured out how to do that, yet.")] + public void InvalidateLayer(KnownLayer knownLayer, DispatcherPriority priority) + { + InvalidateMeasure(priority); + } + + /// + /// Causes the text editor to redraw all lines overlapping with the specified segment. + /// Does nothing if segment is null. + /// + public void Redraw(ISegment segment, DispatcherPriority redrawPriority = DispatcherPriority.Normal) + { + if (segment != null) { + Redraw(segment.Offset, segment.Length, redrawPriority); + } + } + + /// + /// Invalidates all visual lines. + /// The caller of ClearVisualLines() must also call InvalidateMeasure() to ensure + /// that the visual lines will be recreated. + /// + void ClearVisualLines() + { + visibleVisualLines = null; + if (allVisualLines.Count != 0) { + foreach (VisualLine visualLine in allVisualLines) { + DisposeVisualLine(visualLine); + } + allVisualLines.Clear(); + } + } + + void DisposeVisualLine(VisualLine visualLine) + { + if (newVisualLines != null && newVisualLines.Contains(visualLine)) { + throw new ArgumentException("Cannot dispose visual line because it is in construction!"); + } + visibleVisualLines = null; + visualLine.Dispose(); + RemoveInlineObjects(visualLine); + } + #endregion + + #region InvalidateMeasure(DispatcherPriority) + DispatcherOperation invalidateMeasureOperation; + + void InvalidateMeasure(DispatcherPriority priority) + { + if (priority >= DispatcherPriority.Render) { + if (invalidateMeasureOperation != null) { + invalidateMeasureOperation.Abort(); + invalidateMeasureOperation = null; + } + base.InvalidateMeasure(); + } else { + if (invalidateMeasureOperation != null) { + invalidateMeasureOperation.Priority = priority; + } else { + invalidateMeasureOperation = Dispatcher.BeginInvoke( + priority, + new Action( + delegate { + invalidateMeasureOperation = null; + base.InvalidateMeasure(); + } + ) + ); + } + } + } + #endregion + + #region Get(OrConstruct)VisualLine + /// + /// Gets the visual line that contains the document line with the specified number. + /// Returns null if the document line is outside the visible range. + /// + public VisualLine GetVisualLine(int documentLineNumber) + { + // TODO: EnsureVisualLines() ? + foreach (VisualLine visualLine in allVisualLines) { + Debug.Assert(visualLine.IsDisposed == false); + int start = visualLine.FirstDocumentLine.LineNumber; + int end = visualLine.LastDocumentLine.LineNumber; + if (documentLineNumber >= start && documentLineNumber <= end) + return visualLine; + } + return null; + } + + /// + /// Gets the visual line that contains the document line with the specified number. + /// If that line is outside the visible range, a new VisualLine for that document line is constructed. + /// + public VisualLine GetOrConstructVisualLine(DocumentLine documentLine) + { + if (documentLine == null) + throw new ArgumentNullException("documentLine"); + if (!this.Document.Lines.Contains(documentLine)) + throw new InvalidOperationException("Line belongs to wrong document"); + VerifyAccess(); + + VisualLine l = GetVisualLine(documentLine.LineNumber); + if (l == null) { + TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties(); + VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); + + while (heightTree.GetIsCollapsed(documentLine.LineNumber)) { + documentLine = documentLine.PreviousLine; + } + + l = BuildVisualLine(documentLine, + globalTextRunProperties, paragraphProperties, + elementGenerators.ToArray(), lineTransformers.ToArray(), + lastAvailableSize); + allVisualLines.Add(l); + // update all visual top values (building the line might have changed visual top of other lines due to word wrapping) + foreach (var line in allVisualLines) { + line.VisualTop = heightTree.GetVisualPosition(line.FirstDocumentLine); + } + } + return l; + } + #endregion + + #region Visual Lines (fields and properties) + List allVisualLines = new List(); + ReadOnlyCollection visibleVisualLines; + double clippedPixelsOnTop; + List newVisualLines; + + /// + /// Gets the currently visible visual lines. + /// + /// + /// Gets thrown if there are invalid visual lines when this property is accessed. + /// You can use the property to check for this case, + /// or use the method to force creating the visual lines + /// when they are invalid. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + public ReadOnlyCollection VisualLines { + get { + if (visibleVisualLines == null) + { + return new ReadOnlyCollection(new List()); + } + else + { + return visibleVisualLines; + } + } + } + + /// + /// Gets whether the visual lines are valid. + /// Will return false after a call to Redraw(). + /// Accessing the visual lines property will cause a + /// if this property is false. + /// + public bool VisualLinesValid { + get { return visibleVisualLines != null; } + } + + /// + /// Occurs when the TextView is about to be measured and will regenerate its visual lines. + /// This event may be used to mark visual lines as invalid that would otherwise be reused. + /// + public event EventHandler VisualLineConstructionStarting; + + /// + /// Occurs when the TextView was measured and changed its visual lines. + /// + public event EventHandler VisualLinesChanged; + + /// + /// If the visual lines are invalid, creates new visual lines for the visible part + /// of the document. + /// If all visual lines are valid, this method does nothing. + /// + /// The visual line build process is already running. + /// It is not allowed to call this method during the construction of a visual line. + public void EnsureVisualLines() + { + Dispatcher.VerifyAccess(); + if (inMeasure) + throw new InvalidOperationException("The visual line build process is already running! Cannot EnsureVisualLines() during Measure!"); + if (!VisualLinesValid) { + // increase priority for re-measure + InvalidateMeasure(DispatcherPriority.Normal); + // force immediate re-measure + UpdateLayout(); + } + // Sometimes we still have invalid lines after UpdateLayout - work around the problem + // by calling MeasureOverride directly. + if (!VisualLinesValid) { + //Debug.WriteLine("UpdateLayout() failed in EnsureVisualLines"); + //MeasureOverride(lastAvailableSize); + // UpdateLayout(); + } + //if (!VisualLinesValid) + //throw new VisualLinesInvalidException("Internal error: visual lines invalid after EnsureVisualLines call"); + } + #endregion + + #region Measure + /// + /// Additonal amount that allows horizontal scrolling past the end of the longest line. + /// This is necessary to ensure the caret always is visible, even when it is at the end of the longest line. + /// + const double AdditionalHorizontalScrollAmount = 3; + + Size lastAvailableSize; + bool inMeasure; + + /// + protected override Size MeasureOverride(Size availableSize) + { + // We don't support infinite available width, so we'll limit it to 32000 pixels. + if (availableSize.Width > 32000) + availableSize.Width = 32000; + + if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width)) + ClearVisualLines(); + lastAvailableSize = availableSize; + + foreach (UIElement layer in layers) { + layer.Measure(availableSize); + } + MeasureInlineObjects(); + + InvalidateVisual(); // = InvalidateArrange+InvalidateRender + + double maxWidth; + if (document == null) { + // no document -> create empty list of lines + allVisualLines = new List(); + visibleVisualLines = allVisualLines.AsReadOnly(); + maxWidth = 0; + } else { + inMeasure = true; + try { + maxWidth = CreateAndMeasureVisualLines(availableSize); + } finally { + inMeasure = false; + } + } + + // remove inline objects only at the end, so that inline objects that were re-used are not removed from the editor + RemoveInlineObjectsNow(); + + maxWidth += AdditionalHorizontalScrollAmount; + double heightTreeHeight = this.DocumentHeight; + TextEditorOptions options = this.Options; + if (options.AllowScrollBelowDocument) { + if (!double.IsInfinity(scrollViewport.Height)) { + heightTreeHeight = Math.Max(heightTreeHeight, Math.Min(heightTreeHeight - 50, scrollOffset.Y) + scrollViewport.Height); + } + } + + textLayer.SetVisualLines(visibleVisualLines); + + SetScrollData(availableSize, + new Size(maxWidth, heightTreeHeight), + scrollOffset); + if (VisualLinesChanged != null) + VisualLinesChanged(this, EventArgs.Empty); + + return new Size(Math.Min(availableSize.Width, maxWidth), Math.Min(availableSize.Height, heightTreeHeight)); + } + + /// + /// Build all VisualLines in the visible range. + /// + /// Width the longest line + double CreateAndMeasureVisualLines(Size availableSize) + { + TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties(); + VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); + + //Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset); + var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y); + + // number of pixels clipped from the first visual line(s) + clippedPixelsOnTop = scrollOffset.Y - heightTree.GetVisualPosition(firstLineInView); + // clippedPixelsOnTop should be >= 0, except for floating point inaccurracy. + Debug.Assert(clippedPixelsOnTop >= -ExtensionMethods.Epsilon); + + newVisualLines = new List(); + + if (VisualLineConstructionStarting != null) + VisualLineConstructionStarting(this, new VisualLineConstructionStartEventArgs(firstLineInView)); + + var elementGeneratorsArray = elementGenerators.ToArray(); + var lineTransformersArray = lineTransformers.ToArray(); + var nextLine = firstLineInView; + double maxWidth = 0; + double yPos = -clippedPixelsOnTop; + while (yPos < availableSize.Height && nextLine != null) { + VisualLine visualLine = GetVisualLine(nextLine.LineNumber); + if (visualLine == null) { + visualLine = BuildVisualLine(nextLine, + globalTextRunProperties, paragraphProperties, + elementGeneratorsArray, lineTransformersArray, + availableSize); + } + + visualLine.VisualTop = scrollOffset.Y + yPos; + + nextLine = visualLine.LastDocumentLine.NextLine; + + yPos += visualLine.Height; + + foreach (TextLine textLine in visualLine.TextLines) { + if (textLine.WidthIncludingTrailingWhitespace > maxWidth) + maxWidth = textLine.WidthIncludingTrailingWhitespace; + } + + newVisualLines.Add(visualLine); + } + + foreach (VisualLine line in allVisualLines) { + Debug.Assert(line.IsDisposed == false); + if (!newVisualLines.Contains(line)) + DisposeVisualLine(line); + } + + allVisualLines = newVisualLines; + // visibleVisualLines = readonly copy of visual lines + visibleVisualLines = new ReadOnlyCollection(newVisualLines.ToArray()); + newVisualLines = null; + + if (allVisualLines.Any(line => line.IsDisposed)) { + throw new InvalidOperationException("A visual line was disposed even though it is still in use.\n" + + "This can happen when Redraw() is called during measure for lines " + + "that are already constructed."); + } + return maxWidth; + } + #endregion + + #region BuildVisualLine + TextFormatter formatter; + internal TextViewCachedElements cachedElements; + + TextRunProperties CreateGlobalTextRunProperties() + { + var p = new GlobalTextRunProperties(); + p.typeface = this.CreateTypeface(); + p.fontRenderingEmSize = FontSize; + p.foregroundBrush = (Brush)GetValue(Control.ForegroundProperty); + ExtensionMethods.CheckIsFrozen(p.foregroundBrush); + p.cultureInfo = CultureInfo.CurrentCulture; + return p; + } + + VisualLineTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties) + { + return new VisualLineTextParagraphProperties { + defaultTextRunProperties = defaultTextRunProperties, + textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap, + tabSize = Options.IndentationSize * WideSpaceWidth + }; + } + + VisualLine BuildVisualLine(DocumentLine documentLine, + TextRunProperties globalTextRunProperties, + VisualLineTextParagraphProperties paragraphProperties, + VisualLineElementGenerator[] elementGeneratorsArray, + IVisualLineTransformer[] lineTransformersArray, + Size availableSize) + { + if (heightTree.GetIsCollapsed(documentLine.LineNumber)) + throw new InvalidOperationException("Trying to build visual line from collapsed line"); + + //Debug.WriteLine("Building line " + documentLine.LineNumber); + + VisualLine visualLine = new VisualLine(this, documentLine); + VisualLineTextSource textSource = new VisualLineTextSource(visualLine) { + Document = document, + GlobalTextRunProperties = globalTextRunProperties, + TextView = this + }; + + visualLine.ConstructVisualElements(textSource, elementGeneratorsArray); + + if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine) { + // Check whether the lines are collapsed correctly: + double firstLinePos = heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine); + double lastLinePos = heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine); + if (!firstLinePos.IsClose(lastLinePos)) { + for (int i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++) { + if (!heightTree.GetIsCollapsed(i)) + throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed."); + } + throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?"); + } + } + + visualLine.RunTransformers(textSource, lineTransformersArray); + + // now construct textLines: + int textOffset = 0; + TextLineBreak lastLineBreak = null; + var textLines = new List(); + paragraphProperties.indent = 0; + paragraphProperties.firstLineInParagraph = true; + while (textOffset <= visualLine.VisualLengthWithEndOfLineMarker) { + TextLine textLine = formatter.FormatLine( + textSource, + textOffset, + availableSize.Width, + paragraphProperties, + lastLineBreak + ); + textLines.Add(textLine); + textOffset += textLine.Length; + + // exit loop so that we don't do the indentation calculation if there's only a single line + if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker) + break; + + if (paragraphProperties.firstLineInParagraph) { + paragraphProperties.firstLineInParagraph = false; + + TextEditorOptions options = this.Options; + double indentation = 0; + if (options.InheritWordWrapIndentation) { + // determine indentation for next line: + int indentVisualColumn = GetIndentationVisualColumn(visualLine); + if (indentVisualColumn > 0 && indentVisualColumn < textOffset) { + indentation = textLine.GetDistanceFromCharacterHit(new CharacterHit(indentVisualColumn, 0)); + } + } + indentation += options.WordWrapIndentation; + // apply the calculated indentation unless it's more than half of the text editor size: + if (indentation > 0 && indentation * 2 < availableSize.Width) + paragraphProperties.indent = indentation; + } + lastLineBreak = textLine.GetTextLineBreak(); + } + visualLine.SetTextLines(textLines); + heightTree.SetHeight(visualLine.FirstDocumentLine, visualLine.Height); + return visualLine; + } + + static int GetIndentationVisualColumn(VisualLine visualLine) + { + if (visualLine.Elements.Count == 0) + return 0; + int column = 0; + int elementIndex = 0; + VisualLineElement element = visualLine.Elements[elementIndex]; + while (element.IsWhitespace(column)) { + column++; + if (column == element.VisualColumn + element.VisualLength) { + elementIndex++; + if (elementIndex == visualLine.Elements.Count) + break; + element = visualLine.Elements[elementIndex]; + } + } + return column; + } + #endregion + + #region Arrange + /// + /// Arrange implementation. + /// + protected override Size ArrangeOverride(Size finalSize) + { + EnsureVisualLines(); + + foreach (UIElement layer in layers) { + layer.Arrange(new Rect(new Point(0, 0), finalSize)); + } + + if (document == null || allVisualLines.Count == 0) + return finalSize; + + // validate scroll position + Vector newScrollOffset = scrollOffset; + if (scrollOffset.X + finalSize.Width > scrollExtent.Width) { + newScrollOffset.X = Math.Max(0, scrollExtent.Width - finalSize.Width); + } + if (scrollOffset.Y + finalSize.Height > scrollExtent.Height) { + newScrollOffset.Y = Math.Max(0, scrollExtent.Height - finalSize.Height); + } + if (SetScrollData(scrollViewport, scrollExtent, newScrollOffset)) + InvalidateMeasure(DispatcherPriority.Normal); + + //Debug.WriteLine("Arrange finalSize=" + finalSize + ", scrollOffset=" + scrollOffset); + +// double maxWidth = 0; + + if (visibleVisualLines != null) { + Point pos = new Point(-scrollOffset.X, -clippedPixelsOnTop); + foreach (VisualLine visualLine in visibleVisualLines) { + int offset = 0; + foreach (TextLine textLine in visualLine.TextLines) { + foreach (var span in textLine.GetTextRunSpans()) { + InlineObjectRun inline = span.Value as InlineObjectRun; + if (inline != null && inline.VisualLine != null) { + Debug.Assert(inlineObjects.Contains(inline)); + double distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(offset, 0)); + inline.Element.Arrange(new Rect(new Point(pos.X + distance, pos.Y), inline.Element.DesiredSize)); + } + offset += span.Length; + } + pos.Y += textLine.Height; + } + } + } + InvalidateCursor(); + + return finalSize; + } + #endregion + + #region Render + readonly ObserveAddRemoveCollection backgroundRenderers; + + /// + /// Gets the list of background renderers. + /// + public IList BackgroundRenderers { + get { return backgroundRenderers; } + } + + void BackgroundRenderer_Added(IBackgroundRenderer renderer) + { + ConnectToTextView(renderer); + InvalidateLayer(renderer.Layer); + } + + void BackgroundRenderer_Removed(IBackgroundRenderer renderer) + { + DisconnectFromTextView(renderer); + InvalidateLayer(renderer.Layer); + } + + /// + protected override void OnRender(DrawingContext drawingContext) + { + RenderBackground(drawingContext, KnownLayer.Background); + foreach (var line in visibleVisualLines) { + Brush currentBrush = null; + int startVC = 0; + int length = 0; + foreach (var element in line.Elements) { + if (currentBrush == null || !currentBrush.Equals(element.BackgroundBrush)) { + if (currentBrush != null) { + BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder(); + builder.AlignToWholePixels = true; + builder.CornerRadius = 3; + foreach (var rect in BackgroundGeometryBuilder.GetRectsFromVisualSegment(this, line, startVC, startVC + length)) + builder.AddRectangle(this, rect); + Geometry geometry = builder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(currentBrush, null, geometry); + } + } + startVC = element.VisualColumn; + length = element.DocumentLength; + currentBrush = element.BackgroundBrush; + } else { + length += element.VisualLength; + } + } + if (currentBrush != null) { + BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder(); + builder.AlignToWholePixels = true; + builder.CornerRadius = 3; + foreach (var rect in BackgroundGeometryBuilder.GetRectsFromVisualSegment(this, line, startVC, startVC + length)) + builder.AddRectangle(this, rect); + Geometry geometry = builder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(currentBrush, null, geometry); + } + } + } + } + + internal void RenderBackground(DrawingContext drawingContext, KnownLayer layer) + { + foreach (IBackgroundRenderer bg in backgroundRenderers) { + if (bg.Layer == layer) { + bg.Draw(this, drawingContext); + } + } + } + + internal void ArrangeTextLayer(IList visuals) + { + Point pos = new Point(-scrollOffset.X, -clippedPixelsOnTop); + foreach (VisualLineDrawingVisual visual in visuals) { + TranslateTransform t = visual.Transform as TranslateTransform; + if (t == null || t.X != pos.X || t.Y != pos.Y) { + visual.Transform = new TranslateTransform(pos.X, pos.Y); + visual.Transform.Freeze(); + } + pos.Y += visual.Height; + } + } + #endregion + + #region IScrollInfo implementation + /// + /// Size of the document, in pixels. + /// + Size scrollExtent; + + /// + /// Offset of the scroll position. + /// + Vector scrollOffset; + + /// + /// Size of the viewport. + /// + Size scrollViewport; + + void ClearScrollData() + { + SetScrollData(new Size(), new Size(), new Vector()); + } + + bool SetScrollData(Size viewport, Size extent, Vector offset) + { + if (!(viewport.IsClose(this.scrollViewport) + && extent.IsClose(this.scrollExtent) + && offset.IsClose(this.scrollOffset))) + { + this.scrollViewport = viewport; + this.scrollExtent = extent; + SetScrollOffset(offset); + this.OnScrollChange(); + return true; + } + return false; + } + + void OnScrollChange() + { + ScrollViewer scrollOwner = ((IScrollInfo)this).ScrollOwner; + if (scrollOwner != null) { + scrollOwner.InvalidateScrollInfo(); + } + } + + bool canVerticallyScroll; + bool IScrollInfo.CanVerticallyScroll { + get { return canVerticallyScroll; } + set { + if (canVerticallyScroll != value) { + canVerticallyScroll = value; + InvalidateMeasure(DispatcherPriority.Normal); + } + } + } + bool canHorizontallyScroll; + bool IScrollInfo.CanHorizontallyScroll { + get { return canHorizontallyScroll; } + set { + if (canHorizontallyScroll != value) { + canHorizontallyScroll = value; + ClearVisualLines(); + InvalidateMeasure(DispatcherPriority.Normal); + } + } + } + + double IScrollInfo.ExtentWidth { + get { return scrollExtent.Width; } + } + + double IScrollInfo.ExtentHeight { + get { return scrollExtent.Height; } + } + + double IScrollInfo.ViewportWidth { + get { return scrollViewport.Width; } + } + + double IScrollInfo.ViewportHeight { + get { return scrollViewport.Height; } + } + + /// + /// Gets the horizontal scroll offset. + /// + public double HorizontalOffset { + get { return scrollOffset.X; } + } + + /// + /// Gets the vertical scroll offset. + /// + public double VerticalOffset { + get { return scrollOffset.Y; } + } + + /// + /// Gets the scroll offset; + /// + public Vector ScrollOffset { + get { return scrollOffset; } + } + + /// + /// Occurs when the scroll offset has changed. + /// + public event EventHandler ScrollOffsetChanged; + + void SetScrollOffset(Vector vector) + { + if (!canHorizontallyScroll) + vector.X = 0; + if (!canVerticallyScroll) + vector.Y = 0; + + if (!scrollOffset.IsClose(vector)) { + scrollOffset = vector; + if (ScrollOffsetChanged != null) + ScrollOffsetChanged(this, EventArgs.Empty); + } + } + + ScrollViewer IScrollInfo.ScrollOwner { get; set; } + + void IScrollInfo.LineUp() + { + ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y - DefaultLineHeight); + } + + void IScrollInfo.LineDown() + { + ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y + DefaultLineHeight); + } + + void IScrollInfo.LineLeft() + { + ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X - WideSpaceWidth); + } + + void IScrollInfo.LineRight() + { + ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X + WideSpaceWidth); + } + + void IScrollInfo.PageUp() + { + ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y - scrollViewport.Height); + } + + void IScrollInfo.PageDown() + { + ((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y + scrollViewport.Height); + } + + void IScrollInfo.PageLeft() + { + ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X - scrollViewport.Width); + } + + void IScrollInfo.PageRight() + { + ((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X + scrollViewport.Width); + } + + void IScrollInfo.MouseWheelUp() + { + ((IScrollInfo)this).SetVerticalOffset( + scrollOffset.Y - (SystemParameters.WheelScrollLines * DefaultLineHeight)); + OnScrollChange(); + } + + void IScrollInfo.MouseWheelDown() + { + ((IScrollInfo)this).SetVerticalOffset( + scrollOffset.Y + (SystemParameters.WheelScrollLines * DefaultLineHeight)); + OnScrollChange(); + } + + void IScrollInfo.MouseWheelLeft() + { + ((IScrollInfo)this).SetHorizontalOffset( + scrollOffset.X - (SystemParameters.WheelScrollLines * WideSpaceWidth)); + OnScrollChange(); + } + + void IScrollInfo.MouseWheelRight() + { + ((IScrollInfo)this).SetHorizontalOffset( + scrollOffset.X + (SystemParameters.WheelScrollLines * WideSpaceWidth)); + OnScrollChange(); + } + + bool defaultTextMetricsValid; + double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling. + double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling. + double defaultBaseline; // Baseline of a line containing 'x'. Used for TextTop/TextBottom calculation. + + /// + /// Gets the width of a 'wide space' (the space width used for calculating the tab size). + /// + /// + /// This is the width of an 'x' in the current font. + /// We do not measure the width of an actual space as that would lead to tiny tabs in + /// some proportional fonts. + /// For monospaced fonts, this property will return the expected value, as 'x' and ' ' have the same width. + /// + public double WideSpaceWidth { + get { + CalculateDefaultTextMetrics(); + return wideSpaceWidth; + } + } + + /// + /// Gets the default line height. This is the height of an empty line or a line containing regular text. + /// Lines that include formatted text or custom UI elements may have a different line height. + /// + public double DefaultLineHeight { + get { + CalculateDefaultTextMetrics(); + return defaultLineHeight; + } + } + + /// + /// Gets the default baseline position. This is the difference between + /// and for a line containing regular text. + /// Lines that include formatted text or custom UI elements may have a different baseline. + /// + public double DefaultBaseline { + get { + CalculateDefaultTextMetrics(); + return defaultBaseline; + } + } + + void InvalidateDefaultTextMetrics() + { + defaultTextMetricsValid = false; + if (heightTree != null) { + // calculate immediately so that height tree gets updated + CalculateDefaultTextMetrics(); + } + } + + void CalculateDefaultTextMetrics() + { + if (defaultTextMetricsValid) + return; + defaultTextMetricsValid = true; + if (formatter != null) { + var textRunProperties = CreateGlobalTextRunProperties(); + using (var line = formatter.FormatLine( + new SimpleTextSource("x", textRunProperties), + 0, 32000, + new VisualLineTextParagraphProperties { defaultTextRunProperties = textRunProperties }, + null)) + { + wideSpaceWidth = Math.Max(1, line.WidthIncludingTrailingWhitespace); + defaultBaseline = Math.Max(1, line.Baseline); + defaultLineHeight = Math.Max(1, line.Height); + } + } else { + wideSpaceWidth = FontSize / 2; + defaultBaseline = FontSize; + defaultLineHeight = FontSize + 3; + } + // Update heightTree.DefaultLineHeight, if a document is loaded. + if (heightTree != null) + heightTree.DefaultLineHeight = defaultLineHeight; + } + + static double ValidateVisualOffset(double offset) + { + if (double.IsNaN(offset)) + throw new ArgumentException("offset must not be NaN"); + if (offset < 0) + return 0; + else + return offset; + } + + void IScrollInfo.SetHorizontalOffset(double offset) + { + offset = ValidateVisualOffset(offset); + if (!scrollOffset.X.IsClose(offset)) { + SetScrollOffset(new Vector(offset, scrollOffset.Y)); + InvalidateVisual(); + textLayer.InvalidateVisual(); + } + } + + void IScrollInfo.SetVerticalOffset(double offset) + { + offset = ValidateVisualOffset(offset); + if (!scrollOffset.Y.IsClose(offset)) { + SetScrollOffset(new Vector(scrollOffset.X, offset)); + InvalidateMeasure(DispatcherPriority.Normal); + } + } + + Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle) + { + if (rectangle.IsEmpty || visual == null || visual == this || !this.IsAncestorOf(visual)) { + return Rect.Empty; + } + // Convert rectangle into our coordinate space. + GeneralTransform childTransform = visual.TransformToAncestor(this); + rectangle = childTransform.TransformBounds(rectangle); + + MakeVisible(Rect.Offset(rectangle, scrollOffset)); + + return rectangle; + } + + /// + /// Scrolls the text view so that the specified rectangle gets visible. + /// + public void MakeVisible(Rect rectangle) + { + Rect visibleRectangle = new Rect(scrollOffset.X, scrollOffset.Y, + scrollViewport.Width, scrollViewport.Height); + Vector newScrollOffset = scrollOffset; + if (rectangle.Left < visibleRectangle.Left) { + if (rectangle.Right > visibleRectangle.Right) { + newScrollOffset.X = rectangle.Left + rectangle.Width / 2; + } else { + newScrollOffset.X = rectangle.Left; + } + } else if (rectangle.Right > visibleRectangle.Right) { + newScrollOffset.X = rectangle.Right - scrollViewport.Width; + } + if (rectangle.Top < visibleRectangle.Top) { + if (rectangle.Bottom > visibleRectangle.Bottom) { + newScrollOffset.Y = rectangle.Top + rectangle.Height / 2; + } else { + newScrollOffset.Y = rectangle.Top; + } + } else if (rectangle.Bottom > visibleRectangle.Bottom) { + newScrollOffset.Y = rectangle.Bottom - scrollViewport.Height; + } + newScrollOffset.X = ValidateVisualOffset(newScrollOffset.X); + newScrollOffset.Y = ValidateVisualOffset(newScrollOffset.Y); + if (!scrollOffset.IsClose(newScrollOffset)) { + SetScrollOffset(newScrollOffset); + this.OnScrollChange(); + InvalidateMeasure(DispatcherPriority.Normal); + } + } + #endregion + + #region Visual element mouse handling + /// + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + // accept clicks even where the text area draws no background + return new PointHitTestResult(this, hitTestParameters.HitPoint); + } + + [ThreadStatic] static bool invalidCursor; + + /// + /// Updates the mouse cursor by calling , but with input priority. + /// + public static void InvalidateCursor() + { + if (!invalidCursor) { + invalidCursor = true; + Dispatcher.CurrentDispatcher.BeginInvoke( + DispatcherPriority.Input, + new Action( + delegate { + invalidCursor = false; + Mouse.UpdateCursor(); + })); + } + } + + /// + protected override void OnQueryCursor(QueryCursorEventArgs e) + { + VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset); + if (element != null) { + element.OnQueryCursor(e); + } + } + + /// + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + if (!e.Handled) { + EnsureVisualLines(); + VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset); + if (element != null) { + element.OnMouseDown(e); + } + } + } + + /// + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseUp(e); + if (!e.Handled) { + EnsureVisualLines(); + VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset); + if (element != null) { + element.OnMouseUp(e); + } + } + } + #endregion + + #region Getting elements from Visual Position + /// + /// Gets the visual line at the specified document position (relative to start of document). + /// Returns null if there is no visual line for the position (e.g. the position is outside the visible + /// text area). + /// + public VisualLine GetVisualLineFromVisualTop(double visualTop) + { + // TODO: change this method to also work outside the visible range - + // required to make GetPosition work as expected! + EnsureVisualLines(); + foreach (VisualLine vl in this.VisualLines) { + if (visualTop < vl.VisualTop) + continue; + if (visualTop < vl.VisualTop + vl.Height) + return vl; + } + return null; + } + + /// + /// Gets the visual top position (relative to start of document) from a document line number. + /// + public double GetVisualTopByDocumentLine(int line) + { + VerifyAccess(); + if (heightTree == null) + throw ThrowUtil.NoDocumentAssigned(); + return heightTree.GetVisualPosition(heightTree.GetLineByNumber(line)); + } + + VisualLineElement GetVisualLineElementFromPosition(Point visualPosition) + { + VisualLine vl = GetVisualLineFromVisualTop(visualPosition.Y); + if (vl != null) { + int column = vl.GetVisualColumnFloor(visualPosition); +// Debug.WriteLine(vl.FirstDocumentLine.LineNumber + " vc " + column); + foreach (VisualLineElement element in vl.Elements) { + if (element.VisualColumn + element.VisualLength <= column) + continue; + return element; + } + } + return null; + } + #endregion + + #region Visual Position <-> TextViewPosition + /// + /// Gets the visual position from a text view position. + /// + /// The text view position. + /// The mode how to retrieve the Y position. + /// The position in WPF device-independent pixels relative + /// to the top left corner of the document. + public Point GetVisualPosition(TextViewPosition position, VisualYPosition yPositionMode) + { + VerifyAccess(); + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + DocumentLine documentLine = this.Document.GetLineByNumber(position.Line); + VisualLine visualLine = GetOrConstructVisualLine(documentLine); + int visualColumn = position.VisualColumn; + if (visualColumn < 0) { + int offset = documentLine.Offset + position.Column - 1; + visualColumn = visualLine.GetVisualColumn(offset - visualLine.FirstDocumentLine.Offset); + } + return visualLine.GetVisualPosition(visualColumn, yPositionMode); + } + + /// + /// Gets the text view position from the specified visual position. + /// If the position is within a character, it is rounded to the next character boundary. + /// + /// The position in WPF device-independent pixels relative + /// to the top left corner of the document. + /// The logical position, or null if the position is outside the document. + public TextViewPosition? GetPosition(Point visualPosition) + { + VerifyAccess(); + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y); + if (line == null) + return null; + int visualColumn = line.GetVisualColumn(visualPosition); + int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; + return new TextViewPosition(document.GetLocation(documentOffset), visualColumn); + } + + /// + /// Gets the text view position from the specified visual position. + /// If the position is inside a character, the position in front of the character is returned. + /// + /// The position in WPF device-independent pixels relative + /// to the top left corner of the document. + /// The logical position, or null if the position is outside the document. + public TextViewPosition? GetPositionFloor(Point visualPosition) + { + VerifyAccess(); + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y); + if (line == null) + return null; + int visualColumn = line.GetVisualColumnFloor(visualPosition); + int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; + return new TextViewPosition(document.GetLocation(documentOffset), visualColumn); + } + #endregion + + #region Service Provider + readonly ServiceContainer services = new ServiceContainer(); + + /// + /// Gets a service container used to associate services with the text view. + /// + public ServiceContainer Services { + get { return services; } + } + + object IServiceProvider.GetService(Type serviceType) + { + return services.GetService(serviceType); + } + + void ConnectToTextView(object obj) + { + ITextViewConnect c = obj as ITextViewConnect; + if (c != null) + c.AddToTextView(this); + } + + void DisconnectFromTextView(object obj) + { + ITextViewConnect c = obj as ITextViewConnect; + if (c != null) + c.RemoveFromTextView(this); + } + #endregion + + #region MouseHover + /// + /// The PreviewMouseHover event. + /// + public static readonly RoutedEvent PreviewMouseHoverEvent = + EventManager.RegisterRoutedEvent("PreviewMouseHover", RoutingStrategy.Tunnel, + typeof(MouseEventHandler), typeof(TextView)); + /// + /// The MouseHover event. + /// + public static readonly RoutedEvent MouseHoverEvent = + EventManager.RegisterRoutedEvent("MouseHover", RoutingStrategy.Bubble, + typeof(MouseEventHandler), typeof(TextView)); + + /// + /// The PreviewMouseHoverStopped event. + /// + public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = + EventManager.RegisterRoutedEvent("PreviewMouseHoverStopped", RoutingStrategy.Tunnel, + typeof(MouseEventHandler), typeof(TextView)); + /// + /// The MouseHoverStopped event. + /// + public static readonly RoutedEvent MouseHoverStoppedEvent = + EventManager.RegisterRoutedEvent("MouseHoverStopped", RoutingStrategy.Bubble, + typeof(MouseEventHandler), typeof(TextView)); + + + /// + /// Occurs when the mouse has hovered over a fixed location for some time. + /// + public event MouseEventHandler PreviewMouseHover { + add { AddHandler(PreviewMouseHoverEvent, value); } + remove { RemoveHandler(PreviewMouseHoverEvent, value); } + } + + /// + /// Occurs when the mouse has hovered over a fixed location for some time. + /// + public event MouseEventHandler MouseHover { + add { AddHandler(MouseHoverEvent, value); } + remove { RemoveHandler(MouseHoverEvent, value); } + } + + /// + /// Occurs when the mouse had previously hovered but now started moving again. + /// + public event MouseEventHandler PreviewMouseHoverStopped { + add { AddHandler(PreviewMouseHoverStoppedEvent, value); } + remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } + } + + /// + /// Occurs when the mouse had previously hovered but now started moving again. + /// + public event MouseEventHandler MouseHoverStopped { + add { AddHandler(MouseHoverStoppedEvent, value); } + remove { RemoveHandler(MouseHoverStoppedEvent, value); } + } + + MouseHoverLogic hoverLogic; + + void RaiseHoverEventPair(MouseEventArgs e, RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent) + { + var mouseDevice = e.MouseDevice; + var stylusDevice = e.StylusDevice; + int inputTime = Environment.TickCount; + var args1 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) { + RoutedEvent = tunnelingEvent, + Source = this + }; + RaiseEvent(args1); + var args2 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) { + RoutedEvent = bubblingEvent, + Source = this, + Handled = args1.Handled + }; + RaiseEvent(args2); + } + #endregion + + /// + /// Collapses lines for the purpose of scrolling. s marked as collapsed will be hidden + /// and not used to start the generation of a . + /// + /// + /// This method is meant for s that cause s to span + /// multiple s. Do not call it without providing a corresponding + /// . + /// If you want to create collapsible text sections, see . + /// + /// Note that if you want a VisualLineElement to span from line N to line M, then you need to collapse only the lines + /// N+1 to M. Do not collapse line N itself. + /// + /// When you no longer need the section to be collapsed, call on the + /// returned from this method. + /// + public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end) + { + VerifyAccess(); + if (heightTree == null) + throw ThrowUtil.NoDocumentAssigned(); + return heightTree.CollapseText(start, end); + } + + /// + /// Gets the height of the document. + /// + public double DocumentHeight { + get { + // return 0 if there is no document = no heightTree + return heightTree != null ? heightTree.TotalHeight : 0; + } + } + + /// + /// Gets the document line at the specified visual position. + /// + public DocumentLine GetDocumentLineByVisualTop(double visualTop) + { + VerifyAccess(); + if (heightTree == null) + throw ThrowUtil.NoDocumentAssigned(); + return heightTree.GetLineByVisualPosition(visualTop); + } + + /// + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + if (TextFormatterFactory.PropertyChangeAffectsTextFormatter(e.Property)) { + // first, create the new text formatter: + RecreateTextFormatter(); + // changing text formatter requires recreating the cached elements + RecreateCachedElements(); + // and we need to re-measure the font metrics: + InvalidateDefaultTextMetrics(); + } else if (e.Property == Control.ForegroundProperty + || e.Property == TextView.NonPrintableCharacterBrushProperty + || e.Property == TextView.LinkTextBackgroundBrushProperty + || e.Property == TextView.LinkTextForegroundBrushProperty) + { + // changing brushes requires recreating the cached elements + RecreateCachedElements(); + Redraw(); + } + if (e.Property == Control.FontFamilyProperty + || e.Property == Control.FontSizeProperty + || e.Property == Control.FontStretchProperty + || e.Property == Control.FontStyleProperty + || e.Property == Control.FontWeightProperty) + { + // changing font properties requires recreating cached elements + RecreateCachedElements(); + // and we need to re-measure the font metrics: + InvalidateDefaultTextMetrics(); + Redraw(); + } + if (e.Property == ColumnRulerPenProperty) { + columnRulerRenderer.SetRuler(this.Options.ColumnRulerPosition, this.ColumnRulerPen); + } + } + + /// + /// The pen used to draw the column ruler. + /// + /// + public static readonly DependencyProperty ColumnRulerPenProperty = + DependencyProperty.Register("ColumnRulerBrush", typeof(Pen), typeof(TextView), + new FrameworkPropertyMetadata(CreateFrozenPen(Brushes.LightGray))); + + static Pen CreateFrozenPen(SolidColorBrush brush) + { + Pen pen = new Pen(brush, 1); + pen.Freeze(); + return pen; + } + + /// + /// Gets/Sets the pen used to draw the column ruler. + /// + /// + public Pen ColumnRulerPen { + get { return (Pen)GetValue(ColumnRulerPenProperty); } + set { SetValue(ColumnRulerPenProperty, value); } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewCachedElements.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewCachedElements.cs new file mode 100644 index 000000000..c56e22eaf --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewCachedElements.cs @@ -0,0 +1,43 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + sealed class TextViewCachedElements : IDisposable + { + TextFormatter formatter; + Dictionary nonPrintableCharacterTexts; + + public TextLine GetTextForNonPrintableCharacter(string text, ITextRunConstructionContext context) + { + if (nonPrintableCharacterTexts == null) + nonPrintableCharacterTexts = new Dictionary(); + TextLine textLine; + if (!nonPrintableCharacterTexts.TryGetValue(text, out textLine)) { + var p = new VisualLineElementTextRunProperties(context.GlobalTextRunProperties); + p.SetForegroundBrush(context.TextView.NonPrintableCharacterBrush); + if (formatter == null) + formatter = TextFormatterFactory.Create(context.TextView); + textLine = FormattedTextElement.PrepareText(formatter, text, p); + nonPrintableCharacterTexts[text] = textLine; + } + return textLine; + } + + public void Dispose() + { + if (nonPrintableCharacterTexts != null) { + foreach (TextLine line in nonPrintableCharacterTexts.Values) + line.Dispose(); + } + if (formatter != null) + formatter.Dispose(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewWeakEventManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewWeakEventManager.cs new file mode 100644 index 000000000..ca1bb5959 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/TextViewWeakEventManager.cs @@ -0,0 +1,71 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Contains weak event managers for the TextView events. + /// + public static class TextViewWeakEventManager + { + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class DocumentChanged : WeakEventManagerBase + { + /// + protected override void StartListening(TextView source) + { + source.DocumentChanged += DeliverEvent; + } + + /// + protected override void StopListening(TextView source) + { + source.DocumentChanged -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class VisualLinesChanged : WeakEventManagerBase + { + /// + protected override void StartListening(TextView source) + { + source.VisualLinesChanged += DeliverEvent; + } + + /// + protected override void StopListening(TextView source) + { + source.VisualLinesChanged -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class ScrollOffsetChanged : WeakEventManagerBase + { + /// + protected override void StartListening(TextView source) + { + source.ScrollOffsetChanged += DeliverEvent; + } + + /// + protected override void StopListening(TextView source) + { + source.ScrollOffsetChanged -= DeliverEvent; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs new file mode 100644 index 000000000..70727dd07 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs @@ -0,0 +1,681 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System.Linq; +using System.Windows.Controls; +using System.Windows.Media; +using Tango.Scripting.Editors.Utils; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Represents a visual line in the document. + /// A visual line usually corresponds to one DocumentLine, but it can span multiple lines if + /// all but the first are collapsed. + /// + public sealed class VisualLine + { + enum LifetimePhase : byte + { + Generating, + Transforming, + Live, + Disposed + } + + TextView textView; + List elements; + internal bool hasInlineObjects; + LifetimePhase phase; + + /// + /// Gets the document to which this VisualLine belongs. + /// + public TextDocument Document { get; private set; } + + /// + /// Gets the first document line displayed by this visual line. + /// + public DocumentLine FirstDocumentLine { get; private set; } + + /// + /// Gets the last document line displayed by this visual line. + /// + public DocumentLine LastDocumentLine { get; private set; } + + /// + /// Gets a read-only collection of line elements. + /// + public ReadOnlyCollection Elements { get; private set; } + + ReadOnlyCollection textLines; + + /// + /// Gets a read-only collection of text lines. + /// + public ReadOnlyCollection TextLines { + get { + if (phase < LifetimePhase.Live) + throw new InvalidOperationException(); + return textLines; + } + } + + /// + /// Gets the start offset of the VisualLine inside the document. + /// This is equivalent to FirstDocumentLine.Offset. + /// + public int StartOffset { + get { + return FirstDocumentLine.Offset; + } + } + + /// + /// Length in visual line coordinates. + /// + public int VisualLength { get; private set; } + + /// + /// Length in visual line coordinates including the end of line marker, if TextEditorOptions.ShowEndOfLine is enabled. + /// + public int VisualLengthWithEndOfLineMarker { + get { + int length = VisualLength; + if (textView.Options.ShowEndOfLine && LastDocumentLine.NextLine != null) length++; + return length; + } + } + + /// + /// Gets the height of the visual line in device-independent pixels. + /// + public double Height { get; private set; } + + /// + /// Gets the Y position of the line. This is measured in device-independent pixels relative to the start of the document. + /// + public double VisualTop { get; internal set; } + + internal VisualLine(TextView textView, DocumentLine firstDocumentLine) + { + Debug.Assert(textView != null); + Debug.Assert(firstDocumentLine != null); + this.textView = textView; + this.Document = textView.Document; + this.FirstDocumentLine = firstDocumentLine; + } + + internal void ConstructVisualElements(ITextRunConstructionContext context, VisualLineElementGenerator[] generators) + { + Debug.Assert(phase == LifetimePhase.Generating); + foreach (VisualLineElementGenerator g in generators) { + g.StartGeneration(context); + } + elements = new List(); + PerformVisualElementConstruction(generators); + foreach (VisualLineElementGenerator g in generators) { + g.FinishGeneration(); + } + + var globalTextRunProperties = context.GlobalTextRunProperties; + foreach (var element in elements) { + element.SetTextRunProperties(new VisualLineElementTextRunProperties(globalTextRunProperties)); + } + this.Elements = elements.AsReadOnly(); + CalculateOffsets(); + phase = LifetimePhase.Transforming; + } + + void PerformVisualElementConstruction(VisualLineElementGenerator[] generators) + { + TextDocument document = this.Document; + int offset = FirstDocumentLine.Offset; + int currentLineEnd = offset + FirstDocumentLine.Length; + LastDocumentLine = FirstDocumentLine; + int askInterestOffset = 0; // 0 or 1 + while (offset + askInterestOffset <= currentLineEnd) { + int textPieceEndOffset = currentLineEnd; + foreach (VisualLineElementGenerator g in generators) { + g.cachedInterest = g.GetFirstInterestedOffset(offset + askInterestOffset); + if (g.cachedInterest != -1) { + if (g.cachedInterest < offset) + throw new ArgumentOutOfRangeException(g.GetType().Name + ".GetFirstInterestedOffset", + g.cachedInterest, + "GetFirstInterestedOffset must not return an offset less than startOffset. Return -1 to signal no interest."); + if (g.cachedInterest < textPieceEndOffset) + textPieceEndOffset = g.cachedInterest; + } + } + Debug.Assert(textPieceEndOffset >= offset); + if (textPieceEndOffset > offset) { + int textPieceLength = textPieceEndOffset - offset; + elements.Add(new VisualLineText(this, textPieceLength)); + offset = textPieceEndOffset; + } + // If no elements constructed / only zero-length elements constructed: + // do not asking the generators again for the same location (would cause endless loop) + askInterestOffset = 1; + foreach (VisualLineElementGenerator g in generators) { + if (g.cachedInterest == offset) { + VisualLineElement element = g.ConstructElement(offset); + if (element != null) { + elements.Add(element); + if (element.DocumentLength > 0) { + // a non-zero-length element was constructed + askInterestOffset = 0; + offset += element.DocumentLength; + if (offset > currentLineEnd) { + DocumentLine newEndLine = document.GetLineByOffset(offset); + currentLineEnd = newEndLine.Offset + newEndLine.Length; + this.LastDocumentLine = newEndLine; + //if (currentLineEnd < offset) { + // throw new InvalidOperationException( + // "The VisualLineElementGenerator " + g.GetType().Name + + // " produced an element which ends within the line delimiter"); + //} + } + break; + } + } + } + } + } + } + + void CalculateOffsets() + { + int visualOffset = 0; + int textOffset = 0; + foreach (VisualLineElement element in elements) { + element.VisualColumn = visualOffset; + element.RelativeTextOffset = textOffset; + visualOffset += element.VisualLength; + textOffset += element.DocumentLength; + } + VisualLength = visualOffset; + //Debug.Assert(textOffset == LastDocumentLine.EndOffset - FirstDocumentLine.Offset); + } + + internal void RunTransformers(ITextRunConstructionContext context, IVisualLineTransformer[] transformers) + { + Debug.Assert(phase == LifetimePhase.Transforming); + foreach (IVisualLineTransformer transformer in transformers) { + transformer.Transform(context, elements); + } + // For some strange reason, WPF requires that either all or none of the typography properties are set. + if (elements.Any(e => e.TextRunProperties.TypographyProperties != null)) { + // Fix typographic properties + foreach (VisualLineElement element in elements) { + if (element.TextRunProperties.TypographyProperties == null) { + element.TextRunProperties.SetTypographyProperties(new DefaultTextRunTypographyProperties()); + } + } + } + phase = LifetimePhase.Live; + } + + /// + /// Replaces the single element at with the specified elements. + /// The replacement operation must preserve the document length, but may change the visual length. + /// + /// + /// This method may only be called by line transformers. + /// + public void ReplaceElement(int elementIndex, params VisualLineElement[] newElements) + { + ReplaceElement(elementIndex, 1, newElements); + } + + /// + /// Replaces elements starting at with the specified elements. + /// The replacement operation must preserve the document length, but may change the visual length. + /// + /// + /// This method may only be called by line transformers. + /// + public void ReplaceElement(int elementIndex, int count, params VisualLineElement[] newElements) + { + if (phase != LifetimePhase.Transforming) + throw new InvalidOperationException("This method may only be called by line transformers."); + int oldDocumentLength = 0; + for (int i = elementIndex; i < elementIndex + count; i++) { + oldDocumentLength += elements[i].DocumentLength; + } + int newDocumentLength = 0; + foreach (var newElement in newElements) { + newDocumentLength += newElement.DocumentLength; + } + if (oldDocumentLength != newDocumentLength) + throw new InvalidOperationException("Old elements have document length " + oldDocumentLength + ", but new elements have length " + newDocumentLength); + elements.RemoveRange(elementIndex, count); + elements.InsertRange(elementIndex, newElements); + CalculateOffsets(); + } + + internal void SetTextLines(List textLines) + { + this.textLines = textLines.AsReadOnly(); + Height = 0; + foreach (TextLine line in textLines) + Height += line.Height; + } + + /// + /// Gets the visual column from a document offset relative to the first line start. + /// + public int GetVisualColumn(int relativeTextOffset) + { + ThrowUtil.CheckNotNegative(relativeTextOffset, "relativeTextOffset"); + foreach (VisualLineElement element in elements) { + if (element.RelativeTextOffset <= relativeTextOffset + && element.RelativeTextOffset + element.DocumentLength >= relativeTextOffset) + { + return element.GetVisualColumn(relativeTextOffset); + } + } + return VisualLength; + } + + /// + /// Gets the document offset (relative to the first line start) from a visual column. + /// + public int GetRelativeOffset(int visualColumn) + { + ThrowUtil.CheckNotNegative(visualColumn, "visualColumn"); + int documentLength = 0; + foreach (VisualLineElement element in elements) { + if (element.VisualColumn <= visualColumn + && element.VisualColumn + element.VisualLength > visualColumn) + { + return element.GetRelativeOffset(visualColumn); + } + documentLength += element.DocumentLength; + } + return documentLength; + } + + /// + /// Gets the text line containing the specified visual column. + /// + public TextLine GetTextLine(int visualColumn) + { + if (visualColumn < 0) + throw new ArgumentOutOfRangeException("visualColumn"); + if (visualColumn >= VisualLengthWithEndOfLineMarker) + return TextLines[TextLines.Count - 1]; + foreach (TextLine line in TextLines) { + if (visualColumn < line.Length) + return line; + else + visualColumn -= line.Length; + } + throw new InvalidOperationException("Shouldn't happen (VisualLength incorrect?)"); + } + + /// + /// Gets the visual top from the specified text line. + /// + /// Distance in device-independent pixels + /// from the top of the document to the top of the specified text line. + public double GetTextLineVisualYPosition(TextLine textLine, VisualYPosition yPositionMode) + { + if (textLine == null) + throw new ArgumentNullException("textLine"); + double pos = VisualTop; + foreach (TextLine tl in TextLines) { + if (tl == textLine) { + switch (yPositionMode) { + case VisualYPosition.LineTop: + return pos; + case VisualYPosition.LineMiddle: + return pos + tl.Height / 2; + case VisualYPosition.LineBottom: + return pos + tl.Height; + case VisualYPosition.TextTop: + return pos + tl.Baseline - textView.DefaultBaseline; + case VisualYPosition.TextBottom: + return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight; + case VisualYPosition.TextMiddle: + return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight / 2; + case VisualYPosition.Baseline: + return pos + tl.Baseline; + default: + throw new ArgumentException("Invalid yPositionMode:" + yPositionMode); + } + } else { + pos += tl.Height; + } + } + throw new ArgumentException("textLine is not a line in this VisualLine"); + } + + /// + /// Gets the start visual column from the specified text line. + /// + public int GetTextLineVisualStartColumn(TextLine textLine) + { + if (!TextLines.Contains(textLine)) + throw new ArgumentException("textLine is not a line in this VisualLine"); + int col = 0; + foreach (TextLine tl in TextLines) { + if (tl == textLine) + break; + else + col += tl.Length; + } + return col; + } + + /// + /// Gets a TextLine by the visual position. + /// + public TextLine GetTextLineByVisualYPosition(double visualTop) + { + const double epsilon = 0.0001; + double pos = this.VisualTop; + foreach (TextLine tl in TextLines) { + pos += tl.Height; + if (visualTop + epsilon < pos) + return tl; + } + return TextLines[TextLines.Count - 1]; + } + + /// + /// Gets the visual position from the specified visualColumn. + /// + /// Position in device-independent pixels + /// relative to the top left of the document. + public Point GetVisualPosition(int visualColumn, VisualYPosition yPositionMode) + { + TextLine textLine = GetTextLine(visualColumn); + double xPos = GetTextLineVisualXPosition(textLine, visualColumn); + double yPos = GetTextLineVisualYPosition(textLine, yPositionMode); + return new Point(xPos, yPos); + } + + /// + /// Gets the distance to the left border of the text area of the specified visual column. + /// The visual column must belong to the specified text line. + /// + public double GetTextLineVisualXPosition(TextLine textLine, int visualColumn) + { + if (textLine == null) + throw new ArgumentNullException("textLine"); + double xPos = textLine.GetDistanceFromCharacterHit( + new CharacterHit(Math.Min(visualColumn, VisualLengthWithEndOfLineMarker), 0)); + if (visualColumn > VisualLengthWithEndOfLineMarker) { + xPos += (visualColumn - VisualLengthWithEndOfLineMarker) * textView.WideSpaceWidth; + } + return xPos; + } + + /// + /// Gets the visual column from a document position (relative to top left of the document). + /// If the user clicks between two visual columns, rounds to the nearest column. + /// + public int GetVisualColumn(Point point) + { + return GetVisualColumn(point, textView.Options.EnableVirtualSpace); + } + + /// + /// Gets the visual column from a document position (relative to top left of the document). + /// If the user clicks between two visual columns, rounds to the nearest column. + /// + public int GetVisualColumn(Point point, bool allowVirtualSpace) + { + return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace); + } + + /// + /// Gets the visual column from a document position (relative to top left of the document). + /// If the user clicks between two visual columns, rounds to the nearest column. + /// + public int GetVisualColumn(TextLine textLine, double xPos, bool allowVirtualSpace) + { + if (xPos > textLine.WidthIncludingTrailingWhitespace) { + if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) { + int virtualX = (int)Math.Round((xPos - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth); + return VisualLengthWithEndOfLineMarker + virtualX; + } + } + CharacterHit ch = textLine.GetCharacterHitFromDistance(xPos); + return ch.FirstCharacterIndex + ch.TrailingLength; + } + + /// + /// Validates the visual column and returns the correct one. + /// + public int ValidateVisualColumn(TextViewPosition position, bool allowVirtualSpace) + { + return ValidateVisualColumn(Document.GetOffset(position.Location), position.VisualColumn, allowVirtualSpace); + } + + /// + /// Validates the visual column and returns the correct one. + /// + public int ValidateVisualColumn(int offset, int visualColumn, bool allowVirtualSpace) + { + int firstDocumentLineOffset = this.FirstDocumentLine.Offset; + if (visualColumn < 0) { + return GetVisualColumn(offset - firstDocumentLineOffset); + } else { + int offsetFromVisualColumn = GetRelativeOffset(visualColumn); + offsetFromVisualColumn += firstDocumentLineOffset; + if (offsetFromVisualColumn != offset) { + return GetVisualColumn(offset - firstDocumentLineOffset); + } else { + if (visualColumn > VisualLength && !allowVirtualSpace) { + return VisualLength; + } + } + } + return visualColumn; + } + + /// + /// Gets the visual column from a document position (relative to top left of the document). + /// If the user clicks between two visual columns, returns the first of those columns. + /// + public int GetVisualColumnFloor(Point point) + { + return GetVisualColumnFloor(point, textView.Options.EnableVirtualSpace); + } + + /// + /// Gets the visual column from a document position (relative to top left of the document). + /// If the user clicks between two visual columns, returns the first of those columns. + /// + public int GetVisualColumnFloor(Point point, bool allowVirtualSpace) + { + TextLine textLine = GetTextLineByVisualYPosition(point.Y); + if (point.X > textLine.WidthIncludingTrailingWhitespace) { + if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) { + // clicking virtual space in the last line + int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth); + return VisualLengthWithEndOfLineMarker + virtualX; + } else { + // GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character in line + // and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case + // specially and return the line's end column instead. + return GetTextLineVisualStartColumn(textLine) + textLine.Length; + } + } + CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X); + return ch.FirstCharacterIndex; + } + + /// + /// Gets whether the visual line was disposed. + /// + public bool IsDisposed { + get { return phase == LifetimePhase.Disposed; } + } + + internal void Dispose() + { + if (phase == LifetimePhase.Disposed) + return; + Debug.Assert(phase == LifetimePhase.Live); + phase = LifetimePhase.Disposed; + foreach (TextLine textLine in TextLines) { + textLine.Dispose(); + } + } + + /// + /// Gets the next possible caret position after visualColumn, or -1 if there is no caret position. + /// + public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode, bool allowVirtualSpace) + { + if (!HasStopsInVirtualSpace(mode)) + allowVirtualSpace = false; + + if (elements.Count == 0) { + // special handling for empty visual lines: + if (allowVirtualSpace) { + if (direction == LogicalDirection.Forward) + return Math.Max(0, visualColumn + 1); + else if (visualColumn > 0) + return visualColumn - 1; + else + return -1; + } else { + // even though we don't have any elements, + // there's a single caret stop at visualColumn 0 + if (visualColumn < 0 && direction == LogicalDirection.Forward) + return 0; + else if (visualColumn > 0 && direction == LogicalDirection.Backward) + return 0; + else + return -1; + } + } + + int i; + if (direction == LogicalDirection.Backward) { + // Search Backwards: + // If the last element doesn't handle line borders, return the line end as caret stop + + if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) { + if (allowVirtualSpace) + return visualColumn - 1; + else + return this.VisualLength; + } + // skip elements that start after or at visualColumn + for (i = elements.Count - 1; i >= 0; i--) { + if (elements[i].VisualColumn < visualColumn) + break; + } + // search last element that has a caret stop + for (; i >= 0; i--) { + int pos = elements[i].GetNextCaretPosition( + Math.Min(visualColumn, elements[i].VisualColumn + elements[i].VisualLength + 1), + direction, mode); + if (pos >= 0) + return pos; + } + // If we've found nothing, and the first element doesn't handle line borders, + // return the line start as normal caret stop. + if (visualColumn > 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode)) + return 0; + } else { + // Search Forwards: + // If the first element doesn't handle line borders, return the line start as caret stop + if (visualColumn < 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode)) + return 0; + // skip elements that end before or at visualColumn + for (i = 0; i < elements.Count; i++) { + if (elements[i].VisualColumn + elements[i].VisualLength > visualColumn) + break; + } + // search first element that has a caret stop + for (; i < elements.Count; i++) { + int pos = elements[i].GetNextCaretPosition( + Math.Max(visualColumn, elements[i].VisualColumn - 1), + direction, mode); + if (pos >= 0) + return pos; + } + // if we've found nothing, and the last element doesn't handle line borders, + // return the line end as caret stop + if ((allowVirtualSpace || !elements[elements.Count-1].HandlesLineBorders) && HasImplicitStopAtLineEnd(mode)) { + if (visualColumn < this.VisualLength) + return this.VisualLength; + else if (allowVirtualSpace) + return visualColumn + 1; + } + } + // we've found nothing, return -1 and let the caret search continue in the next line + return -1; + } + + static bool HasStopsInVirtualSpace(CaretPositioningMode mode) + { + return mode == CaretPositioningMode.Normal; + } + + static bool HasImplicitStopAtLineStart(CaretPositioningMode mode) + { + return mode == CaretPositioningMode.Normal; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "mode", + Justification = "make method consistent with HasImplicitStopAtLineStart; might depend on mode in the future")] + static bool HasImplicitStopAtLineEnd(CaretPositioningMode mode) + { + return true; + } + + VisualLineDrawingVisual visual; + + internal VisualLineDrawingVisual Render() + { + Debug.Assert(phase == LifetimePhase.Live); + if (visual == null) + visual = new VisualLineDrawingVisual(this); + return visual; + } + } + + sealed class VisualLineDrawingVisual : DrawingVisual + { + public readonly VisualLine VisualLine; + public readonly double Height; + internal bool IsAdded; + + public VisualLineDrawingVisual(VisualLine visualLine) + { + this.VisualLine = visualLine; + var drawingContext = RenderOpen(); + double pos = 0; + foreach (TextLine textLine in visualLine.TextLines) { + textLine.Draw(drawingContext, new Point(0, pos), InvertAxes.None); + pos += textLine.Height; + } + this.Height = pos; + drawingContext.Close(); + } + + protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters) + { + return null; + } + + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + return null; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineConstructionStartEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineConstructionStartEventArgs.cs new file mode 100644 index 000000000..438e516bd --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineConstructionStartEventArgs.cs @@ -0,0 +1,29 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// EventArgs for the event. + /// + public class VisualLineConstructionStartEventArgs : EventArgs + { + /// + /// Gets/Sets the first line that is visible in the TextView. + /// + public DocumentLine FirstLineInView { get; private set; } + + /// + /// Creates a new VisualLineConstructionStartEventArgs instance. + /// + public VisualLineConstructionStartEventArgs(DocumentLine firstLineInView) + { + if (firstLineInView == null) + throw new ArgumentNullException("firstLineInView"); + this.FirstLineInView = firstLineInView; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElement.cs new file mode 100644 index 000000000..7858ac038 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElement.cs @@ -0,0 +1,249 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Represents a visual element in the document. + /// + public abstract class VisualLineElement + { + /// + /// Creates a new VisualLineElement. + /// + /// The length of the element in VisualLine coordinates. Must be positive. + /// The length of the element in the document. Must be non-negative. + protected VisualLineElement(int visualLength, int documentLength) + { + if (visualLength < 1) + throw new ArgumentOutOfRangeException("visualLength", visualLength, "Value must be at least 1"); + if (documentLength < 0) + throw new ArgumentOutOfRangeException("documentLength", documentLength, "Value must be at least 0"); + this.VisualLength = visualLength; + this.DocumentLength = documentLength; + } + + /// + /// Gets the length of this element in visual columns. + /// + public int VisualLength { get; private set; } + + /// + /// Gets the length of this element in the text document. + /// + public int DocumentLength { get; private set; } + + /// + /// Gets the visual column where this element starts. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", + Justification = "This property holds the start visual column, use GetVisualColumn to get inner visual columns.")] + public int VisualColumn { get; internal set; } + + /// + /// Gets the text offset where this element starts, relative to the start text offset of the visual line. + /// + public int RelativeTextOffset { get; internal set; } + + /// + /// Gets the text run properties. + /// A unique instance is used for each + /// ; colorizing code may assume that modifying the + /// will affect only this + /// . + /// + public VisualLineElementTextRunProperties TextRunProperties { get; private set; } + + /// + /// Gets/sets the brush used for the background of this . + /// + public Brush BackgroundBrush { get; set; } + + internal void SetTextRunProperties(VisualLineElementTextRunProperties p) + { + this.TextRunProperties = p; + } + + /// + /// Creates the TextRun for this line element. + /// + /// + /// The visual column from which the run should be constructed. + /// Normally the same value as the property is used to construct the full run; + /// but when word-wrapping is active, partial runs might be created. + /// + /// + /// Context object that contains information relevant for text run creation. + /// + public abstract TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context); + + /// + /// Retrieves the text span immediately before the visual column. + /// + /// This method is used for word-wrapping in bidirectional text. + public virtual TextSpan GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context) + { + return null; + } + + /// + /// Gets if this VisualLineElement can be split. + /// + public virtual bool CanSplit { + get { return false; } + } + + /// + /// Splits the element. + /// + /// Position inside this element at which it should be broken + /// The collection of line elements + /// The index at which this element is in the elements list. + public virtual void Split(int splitVisualColumn, IList elements, int elementIndex) + { + throw new NotSupportedException(); + } + + /// + /// Helper method for splitting this line element into two, correctly updating the + /// , , + /// and properties. + /// + /// The element before the split position. + /// The element after the split position. + /// The split position as visual column. + /// The split position as text offset. + protected void SplitHelper(VisualLineElement firstPart, VisualLineElement secondPart, int splitVisualColumn, int splitRelativeTextOffset) + { + if (firstPart == null) + throw new ArgumentNullException("firstPart"); + if (secondPart == null) + throw new ArgumentNullException("secondPart"); + int relativeSplitVisualColumn = splitVisualColumn - VisualColumn; + int relativeSplitRelativeTextOffset = splitRelativeTextOffset - RelativeTextOffset; + + if (relativeSplitVisualColumn <= 0 || relativeSplitVisualColumn >= VisualLength) + throw new ArgumentOutOfRangeException("splitVisualColumn", splitVisualColumn, "Value must be between " + (VisualColumn + 1) + " and " + (VisualColumn + VisualLength - 1)); + if (relativeSplitRelativeTextOffset < 0 || relativeSplitRelativeTextOffset > DocumentLength) + throw new ArgumentOutOfRangeException("splitRelativeTextOffset", splitRelativeTextOffset, "Value must be between " + (RelativeTextOffset) + " and " + (RelativeTextOffset + DocumentLength)); + int oldVisualLength = VisualLength; + int oldDocumentLength = DocumentLength; + int oldVisualColumn = VisualColumn; + int oldRelativeTextOffset = RelativeTextOffset; + firstPart.VisualColumn = oldVisualColumn; + secondPart.VisualColumn = oldVisualColumn + relativeSplitVisualColumn; + firstPart.RelativeTextOffset = oldRelativeTextOffset; + secondPart.RelativeTextOffset = oldRelativeTextOffset + relativeSplitRelativeTextOffset; + firstPart.VisualLength = relativeSplitVisualColumn; + secondPart.VisualLength = oldVisualLength - relativeSplitVisualColumn; + firstPart.DocumentLength = relativeSplitRelativeTextOffset; + secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset; + if (firstPart.TextRunProperties == null) + firstPart.TextRunProperties = TextRunProperties.Clone(); + if (secondPart.TextRunProperties == null) + secondPart.TextRunProperties = TextRunProperties.Clone(); + } + + /// + /// Gets the visual column of a text location inside this element. + /// The text offset is given relative to the visual line start. + /// + public virtual int GetVisualColumn(int relativeTextOffset) + { + if (relativeTextOffset >= this.RelativeTextOffset + DocumentLength) + return VisualColumn + VisualLength; + else + return VisualColumn; + } + + /// + /// Gets the text offset of a visual column inside this element. + /// + /// A text offset relative to the visual line start. + public virtual int GetRelativeOffset(int visualColumn) + { + if (visualColumn >= this.VisualColumn + VisualLength) + return RelativeTextOffset + DocumentLength; + else + return RelativeTextOffset; + } + + /// + /// Gets the next caret position inside this element. + /// + /// The visual column from which the search should be started. + /// The search direction (forwards or backwards). + /// Whether to stop only at word borders. + /// The visual column of the next caret position, or -1 if there is no next caret position. + /// + /// In the space between two line elements, it is sufficient that one of them contains a caret position; + /// though in many cases, both of them contain one. + /// + public virtual int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) + { + int stop1 = this.VisualColumn; + int stop2 = this.VisualColumn + this.VisualLength; + if (direction == LogicalDirection.Backward) { + if (visualColumn > stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) + return stop2; + else if (visualColumn > stop1) + return stop1; + } else { + if (visualColumn < stop1) + return stop1; + else if (visualColumn < stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) + return stop2; + } + return -1; + } + + /// + /// Gets whether the specified offset in this element is considered whitespace. + /// + public virtual bool IsWhitespace(int visualColumn) + { + return false; + } + + /// + /// Gets whether the implementation handles line borders. + /// If this property returns false, the caller of GetNextCaretPosition should handle the line + /// borders (i.e. place caret stops at the start and end of the line). + /// This property has an effect only for VisualLineElements that are at the start or end of a + /// . + /// + public virtual bool HandlesLineBorders { + get { return false; } + } + + /// + /// Queries the cursor over the visual line element. + /// + protected internal virtual void OnQueryCursor(QueryCursorEventArgs e) + { + } + + /// + /// Allows the visual line element to handle a mouse event. + /// + protected internal virtual void OnMouseDown(MouseButtonEventArgs e) + { + } + + /// + /// Allows the visual line element to handle a mouse event. + /// + protected internal virtual void OnMouseUp(MouseButtonEventArgs e) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementGenerator.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementGenerator.cs new file mode 100644 index 000000000..8b2f7ac5b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementGenerator.cs @@ -0,0 +1,63 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// Abstract base class for generators that produce new visual line elements. + /// + public abstract class VisualLineElementGenerator + { + /// + /// Gets the text run construction context. + /// + protected ITextRunConstructionContext CurrentContext { get; private set; } + + /// + /// Initializes the generator for the + /// + public virtual void StartGeneration(ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + this.CurrentContext = context; + } + + /// + /// De-initializes the generator. + /// + public virtual void FinishGeneration() + { + this.CurrentContext = null; + } + + /// + /// Should only be used by VisualLine.ConstructVisualElements. + /// + internal int cachedInterest; + + /// + /// Gets the first offset >= startOffset where the generator wants to construct an element. + /// Return -1 to signal no interest. + /// + public abstract int GetFirstInterestedOffset(int startOffset); + + /// + /// Constructs an element at the specified offset. + /// May return null if no element should be constructed. + /// + /// + /// Avoid signalling interest and then building no element by returning null - doing so + /// causes the generated elements to be unnecessarily split + /// at the position where you signalled interest. + /// + public abstract VisualLineElement ConstructElement(int offset); + } + + internal interface IBuiltinElementGenerator + { + void FetchOptions(TextEditorOptions options); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementTextRunProperties.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementTextRunProperties.cs new file mode 100644 index 000000000..a39276c5b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElementTextRunProperties.cs @@ -0,0 +1,241 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// implementation that allows changing the properties. + /// A instance usually is assigned to a single + /// . + /// + public class VisualLineElementTextRunProperties : TextRunProperties, ICloneable + { + Brush backgroundBrush; + BaselineAlignment baselineAlignment; + CultureInfo cultureInfo; + double fontHintingEmSize; + double fontRenderingEmSize; + Brush foregroundBrush; + Typeface typeface; + TextDecorationCollection textDecorations; + TextEffectCollection textEffects; + TextRunTypographyProperties typographyProperties; + NumberSubstitution numberSubstitution; + + /// + /// Creates a new VisualLineElementTextRunProperties instance that copies its values + /// from the specified . + /// For the and collections, deep copies + /// are created if those collections are not frozen. + /// + public VisualLineElementTextRunProperties(TextRunProperties textRunProperties) + { + if (textRunProperties == null) + throw new ArgumentNullException("textRunProperties"); + backgroundBrush = textRunProperties.BackgroundBrush; + baselineAlignment = textRunProperties.BaselineAlignment; + cultureInfo = textRunProperties.CultureInfo; + fontHintingEmSize = textRunProperties.FontHintingEmSize; + fontRenderingEmSize = textRunProperties.FontRenderingEmSize; + foregroundBrush = textRunProperties.ForegroundBrush; + typeface = textRunProperties.Typeface; + textDecorations = textRunProperties.TextDecorations; + if (textDecorations != null && !textDecorations.IsFrozen) { + textDecorations = textDecorations.Clone(); + } + textEffects = textRunProperties.TextEffects; + if (textEffects != null && !textEffects.IsFrozen) { + textEffects = textEffects.Clone(); + } + typographyProperties = textRunProperties.TypographyProperties; + numberSubstitution = textRunProperties.NumberSubstitution; + } + + /// + /// Creates a copy of this instance. + /// + public virtual VisualLineElementTextRunProperties Clone() + { + return new VisualLineElementTextRunProperties(this); + } + + object ICloneable.Clone() + { + return Clone(); + } + + /// + public override Brush BackgroundBrush { + get { return backgroundBrush; } + } + + /// + /// Sets the . + /// + public void SetBackgroundBrush(Brush value) + { + ExtensionMethods.CheckIsFrozen(value); + backgroundBrush = value; + } + + /// + public override BaselineAlignment BaselineAlignment { + get { return baselineAlignment; } + } + + /// + /// Sets the . + /// + public void SetBaselineAlignment(BaselineAlignment value) + { + baselineAlignment = value; + } + + /// + public override CultureInfo CultureInfo { + get { return cultureInfo; } + } + + /// + /// Sets the . + /// + public void SetCultureInfo(CultureInfo value) + { + if (value == null) + throw new ArgumentNullException("value"); + cultureInfo = value; + } + + /// + public override double FontHintingEmSize { + get { return fontHintingEmSize; } + } + + /// + /// Sets the . + /// + public void SetFontHintingEmSize(double value) + { + fontHintingEmSize = value; + } + + /// + public override double FontRenderingEmSize { + get { return fontRenderingEmSize; } + } + + /// + /// Sets the . + /// + public void SetFontRenderingEmSize(double value) + { + fontRenderingEmSize = value; + } + + /// + public override Brush ForegroundBrush { + get { return foregroundBrush; } + } + + /// + /// Sets the . + /// + public void SetForegroundBrush(Brush value) + { + ExtensionMethods.CheckIsFrozen(value); + foregroundBrush = value; + } + + /// + public override Typeface Typeface { + get { return typeface; } + } + + /// + /// Sets the . + /// + public void SetTypeface(Typeface value) + { + if (value == null) + throw new ArgumentNullException("value"); + typeface = value; + } + + /// + /// Gets the text decorations. The value may be null, a frozen + /// or an unfrozen . + /// If the value is an unfrozen , you may assume that the + /// collection instance is only used for this instance and it is safe + /// to add s. + /// + public override TextDecorationCollection TextDecorations { + get { return textDecorations; } + } + + /// + /// Sets the . + /// + public void SetTextDecorations(TextDecorationCollection value) + { + ExtensionMethods.CheckIsFrozen(value); + textDecorations = value; + } + + /// + /// Gets the text effects. The value may be null, a frozen + /// or an unfrozen . + /// If the value is an unfrozen , you may assume that the + /// collection instance is only used for this instance and it is safe + /// to add s. + /// + public override TextEffectCollection TextEffects { + get { return textEffects; } + } + + /// + /// Sets the . + /// + public void SetTextEffects(TextEffectCollection value) + { + ExtensionMethods.CheckIsFrozen(value); + textEffects = value; + } + + /// + /// Gets the typography properties for the text run. + /// + public override TextRunTypographyProperties TypographyProperties { + get { return typographyProperties; } + } + + /// + /// Sets the . + /// + public void SetTypographyProperties(TextRunTypographyProperties value) + { + typographyProperties = value; + } + + /// + /// Gets the number substitution settings for the text run. + /// + public override NumberSubstitution NumberSubstitution { + get { return numberSubstitution; } + } + + /// + /// Sets the . + /// + public void SetNumberSubstitution(NumberSubstitution value) + { + numberSubstitution = value; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineLinkText.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineLinkText.cs new file mode 100644 index 000000000..57c472641 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineLinkText.cs @@ -0,0 +1,114 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using System.Windows.Navigation; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// VisualLineElement that represents a piece of text and is a clickable link. + /// + public class VisualLineLinkText : VisualLineText + { + /// + /// Gets/Sets the URL that is navigated to when the link is clicked. + /// + public Uri NavigateUri { get; set; } + + /// + /// Gets/Sets the window name where the URL will be opened. + /// + public string TargetName { get; set; } + + /// + /// Gets/Sets whether the user needs to press Control to click the link. + /// The default value is true. + /// + public bool RequireControlModifierForClick { get; set; } + + /// + /// Creates a visual line text element with the specified length. + /// It uses the and its + /// to find the actual text string. + /// + public VisualLineLinkText(VisualLine parentVisualLine, int length) : base(parentVisualLine, length) + { + this.RequireControlModifierForClick = true; + } + + /// + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + this.TextRunProperties.SetForegroundBrush(context.TextView.LinkTextForegroundBrush); + this.TextRunProperties.SetBackgroundBrush(context.TextView.LinkTextBackgroundBrush); + this.TextRunProperties.SetTextDecorations(TextDecorations.Underline); + return base.CreateTextRun(startVisualColumn, context); + } + + /// + /// Gets whether the link is currently clickable. + /// + /// Returns true when control is pressed; or when + /// is disabled. + protected bool LinkIsClickable() + { + if (NavigateUri == null) + return false; + if (RequireControlModifierForClick) + return (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; + else + return true; + } + + /// + protected internal override void OnQueryCursor(QueryCursorEventArgs e) + { + if (LinkIsClickable()) { + e.Handled = true; + e.Cursor = Cursors.Hand; + } + } + + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "I've seen Process.Start throw undocumented exceptions when the mail client / web browser is installed incorrectly")] + protected internal override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left && !e.Handled && LinkIsClickable()) { + RequestNavigateEventArgs args = new RequestNavigateEventArgs(this.NavigateUri, this.TargetName); + args.RoutedEvent = Hyperlink.RequestNavigateEvent; + FrameworkElement element = e.Source as FrameworkElement; + if (element != null) { + // allow user code to handle the navigation request + element.RaiseEvent(args); + } + if (!args.Handled) { + try { + Process.Start(this.NavigateUri.ToString()); + } catch { + // ignore all kinds of errors during web browser start + } + } + e.Handled = true; + } + } + + /// + protected override VisualLineText CreateInstance(int length) + { + return new VisualLineLinkText(ParentVisualLine, length) { + NavigateUri = this.NavigateUri, + TargetName = this.TargetName, + RequireControlModifierForClick = this.RequireControlModifierForClick + }; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineText.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineText.cs new file mode 100644 index 000000000..85b0a9ac4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineText.cs @@ -0,0 +1,122 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows.Documents; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// VisualLineElement that represents a piece of text. + /// + public class VisualLineText : VisualLineElement + { + VisualLine parentVisualLine; + + /// + /// Gets the parent visual line. + /// + public VisualLine ParentVisualLine { + get { return parentVisualLine; } + } + + /// + /// Creates a visual line text element with the specified length. + /// It uses the and its + /// to find the actual text string. + /// + public VisualLineText(VisualLine parentVisualLine, int length) : base(length, length) + { + if (parentVisualLine == null) + throw new ArgumentNullException("parentVisualLine"); + this.parentVisualLine = parentVisualLine; + } + + /// + /// Override this method to control the type of new VisualLineText instances when + /// the visual line is split due to syntax highlighting. + /// + protected virtual VisualLineText CreateInstance(int length) + { + return new VisualLineText(parentVisualLine, length); + } + + /// + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + + int relativeOffset = startVisualColumn - VisualColumn; + StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset); + return new TextCharacters(text.Text, text.Offset, text.Count, this.TextRunProperties); + } + + /// + public override bool IsWhitespace(int visualColumn) + { + int offset = visualColumn - this.VisualColumn + parentVisualLine.FirstDocumentLine.Offset + this.RelativeTextOffset; + return char.IsWhiteSpace(parentVisualLine.Document.GetCharAt(offset)); + } + + /// + public override TextSpan GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + + int relativeOffset = visualColumnLimit - VisualColumn; + StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset); + CharacterBufferRange range = new CharacterBufferRange(text.Text, text.Offset, text.Count); + return new TextSpan(range.Length, new CultureSpecificCharacterBufferRange(this.TextRunProperties.CultureInfo, range)); + } + + /// + public override bool CanSplit { + get { return true; } + } + + /// + public override void Split(int splitVisualColumn, IList elements, int elementIndex) + { + if (splitVisualColumn <= VisualColumn || splitVisualColumn >= VisualColumn + VisualLength) + throw new ArgumentOutOfRangeException("splitVisualColumn", splitVisualColumn, "Value must be between " + (VisualColumn + 1) + " and " + (VisualColumn + VisualLength - 1)); + if (elements == null) + throw new ArgumentNullException("elements"); + if (elements[elementIndex] != this) + throw new ArgumentException("Invalid elementIndex - couldn't find this element at the index"); + int relativeSplitPos = splitVisualColumn - VisualColumn; + VisualLineText splitPart = CreateInstance(DocumentLength - relativeSplitPos); + SplitHelper(this, splitPart, splitVisualColumn, relativeSplitPos + RelativeTextOffset); + elements.Insert(elementIndex + 1, splitPart); + } + + /// + public override int GetRelativeOffset(int visualColumn) + { + return this.RelativeTextOffset + visualColumn - this.VisualColumn; + } + + /// + public override int GetVisualColumn(int relativeTextOffset) + { + return this.VisualColumn + relativeTextOffset - this.RelativeTextOffset; + } + + /// + public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) + { + int textOffset = parentVisualLine.StartOffset + this.RelativeTextOffset; + int pos = TextUtilities.GetNextCaretPosition(parentVisualLine.Document, textOffset + visualColumn - this.VisualColumn, direction, mode); + if (pos < textOffset || pos > textOffset + this.DocumentLength) + return -1; + else + return this.VisualColumn + pos - textOffset; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextParagraphProperties.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextParagraphProperties.cs new file mode 100644 index 000000000..9c3aab87f --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextParagraphProperties.cs @@ -0,0 +1,31 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media.TextFormatting; + +namespace Tango.Scripting.Editors.Rendering +{ + sealed class VisualLineTextParagraphProperties : TextParagraphProperties + { + internal TextRunProperties defaultTextRunProperties; + internal TextWrapping textWrapping; + internal double tabSize; + internal double indent; + internal bool firstLineInParagraph; + + public override double DefaultIncrementalTab { + get { return tabSize; } + } + + public override FlowDirection FlowDirection { get { return FlowDirection.LeftToRight; } } + public override TextAlignment TextAlignment { get { return TextAlignment.Left; } } + public override double LineHeight { get { return double.NaN; } } + public override bool FirstLineInParagraph { get { return firstLineInParagraph; } } + public override TextRunProperties DefaultTextRunProperties { get { return defaultTextRunProperties; } } + public override TextWrapping TextWrapping { get { return textWrapping; } } + public override TextMarkerProperties TextMarkerProperties { get { return null; } } + public override double Indent { get { return indent; } } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextSource.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextSource.cs new file mode 100644 index 000000000..3a5167010 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineTextSource.cs @@ -0,0 +1,123 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows.Media.TextFormatting; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// WPF TextSource implementation that creates TextRuns for a VisualLine. + /// + sealed class VisualLineTextSource : TextSource, ITextRunConstructionContext + { + public VisualLineTextSource(VisualLine visualLine) + { + this.VisualLine = visualLine; + } + + public VisualLine VisualLine { get; private set; } + public TextView TextView { get; set; } + public TextDocument Document { get; set; } + public TextRunProperties GlobalTextRunProperties { get; set; } + + public override TextRun GetTextRun(int textSourceCharacterIndex) + { + try { + foreach (VisualLineElement element in VisualLine.Elements) { + if (textSourceCharacterIndex >= element.VisualColumn + && textSourceCharacterIndex < element.VisualColumn + element.VisualLength) + { + int relativeOffset = textSourceCharacterIndex - element.VisualColumn; + TextRun run = element.CreateTextRun(textSourceCharacterIndex, this); + if (run == null) + throw new ArgumentNullException(element.GetType().Name + ".CreateTextRun"); + if (run.Length == 0) + throw new ArgumentException("The returned TextRun must not have length 0.", element.GetType().Name + ".Length"); + if (relativeOffset + run.Length > element.VisualLength) + throw new ArgumentException("The returned TextRun is too long.", element.GetType().Name + ".CreateTextRun"); + InlineObjectRun inlineRun = run as InlineObjectRun; + if (inlineRun != null) { + inlineRun.VisualLine = VisualLine; + VisualLine.hasInlineObjects = true; + TextView.AddInlineObject(inlineRun); + } + return run; + } + } + if (TextView.Options.ShowEndOfLine && textSourceCharacterIndex == VisualLine.VisualLength) { + return CreateTextRunForNewLine(); + } + return new TextEndOfParagraph(1); + } catch (Exception ex) { + Debug.WriteLine(ex.ToString()); + throw; + } + } + + TextRun CreateTextRunForNewLine() + { + string newlineText = ""; + DocumentLine lastDocumentLine = VisualLine.LastDocumentLine; + if (lastDocumentLine.DelimiterLength == 2) { + newlineText = "¶"; + } else if (lastDocumentLine.DelimiterLength == 1) { + char newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length); + if (newlineChar == '\r') + newlineText = "\\r"; + else if (newlineChar == '\n') + newlineText = "\\n"; + else + newlineText = "?"; + } + return new FormattedTextRun(new FormattedTextElement(TextView.cachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties); + } + + public override TextSpan GetPrecedingText(int textSourceCharacterIndexLimit) + { + try { + foreach (VisualLineElement element in VisualLine.Elements) { + if (textSourceCharacterIndexLimit > element.VisualColumn + && textSourceCharacterIndexLimit <= element.VisualColumn + element.VisualLength) + { + TextSpan span = element.GetPrecedingText(textSourceCharacterIndexLimit, this); + if (span == null) + break; + int relativeOffset = textSourceCharacterIndexLimit - element.VisualColumn; + if (span.Length > relativeOffset) + throw new ArgumentException("The returned TextSpan is too long.", element.GetType().Name + ".GetPrecedingText"); + return span; + } + } + CharacterBufferRange empty = CharacterBufferRange.Empty; + return new TextSpan(empty.Length, new CultureSpecificCharacterBufferRange(null, empty)); + } catch (Exception ex) { + Debug.WriteLine(ex.ToString()); + throw; + } + } + + public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int textSourceCharacterIndex) + { + throw new NotSupportedException(); + } + + string cachedString; + int cachedStringOffset; + + public StringSegment GetText(int offset, int length) + { + if (cachedString != null) { + if (offset >= cachedStringOffset && offset + length <= cachedStringOffset + cachedString.Length) { + return new StringSegment(cachedString, offset - cachedStringOffset, length); + } + } + cachedStringOffset = offset; + return new StringSegment(cachedString = this.Document.GetText(offset, length)); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLinesInvalidException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLinesInvalidException.cs new file mode 100644 index 000000000..bfbae27b0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLinesInvalidException.cs @@ -0,0 +1,44 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Runtime.Serialization; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// A VisualLinesInvalidException indicates that you accessed the property + /// of the while the visual lines were invalid. + /// + [Serializable] + public class VisualLinesInvalidException : Exception + { + /// + /// Creates a new VisualLinesInvalidException instance. + /// + public VisualLinesInvalidException() : base() + { + } + + /// + /// Creates a new VisualLinesInvalidException instance. + /// + public VisualLinesInvalidException(string message) : base(message) + { + } + + /// + /// Creates a new VisualLinesInvalidException instance. + /// + public VisualLinesInvalidException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Creates a new VisualLinesInvalidException instance. + /// + protected VisualLinesInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualYPosition.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualYPosition.cs new file mode 100644 index 000000000..352a3eaa3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualYPosition.cs @@ -0,0 +1,46 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Rendering +{ + /// + /// An enum that specifies the possible Y positions that can be returned by VisualLine.GetVisualPosition. + /// + public enum VisualYPosition + { + /// + /// Returns the top of the TextLine. + /// + LineTop, + /// + /// Returns the top of the text. + /// If the line contains inline UI elements larger than the text, TextTop may be below LineTop. + /// For a line containing regular text (all in the editor's main font), this will be equal to LineTop. + /// + TextTop, + /// + /// Returns the bottom of the TextLine. + /// + LineBottom, + /// + /// The middle between LineTop and LineBottom. + /// + LineMiddle, + /// + /// Returns the bottom of the text. + /// If the line contains inline UI elements larger than the text, TextBottom might be above LineBottom. + /// For a line containing regular text (all in the editor's main font), this will be equal to LineBottom. + /// + TextBottom, + /// + /// The middle between TextTop and TextBottom. + /// + TextMiddle, + /// + /// Returns the baseline of the text. + /// + Baseline + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs new file mode 100644 index 000000000..efa1b087a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs @@ -0,0 +1,1595 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; +using System.Xml; +using Tango.Core.Commands; +using Tango.Scripting.Editors.CodeCompletion; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Folding; +using Tango.Scripting.Editors.Highlighting; +using Tango.Scripting.Editors.Highlighting.Xshd; +using Tango.Scripting.Editors.Intellisense; +using Tango.Scripting.Editors.Popups; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Parsing; + +namespace Tango.Scripting.Editors +{ + public class ScriptEditor : TextEditor + { + private char[] word_separators = { ' ', '\t', '\n', '.', '(', ',', '-', '*', '/', '+', '$', '=', '<', '>' }; + private string[] _blocking_type_words = { "class", "void" }; + + private DispatcherTimer _update_timer; + private Popup _popup; + private FoldingManager foldingManager; + private BraceFoldingStrategy foldingStrategy; + private CompletionWindow completionWindow; + private ScriptParser _parser; + private List _current_usings; + private List _knownTypes; + private List _declaredTypes; + private bool _isLoadingTypes; + + #region Mini Classes + + private class ScriptClass + { + public String Name { get; set; } + public int Index { get; set; } + } + + private class ConstructionSession + { + public KnownType Type { get; set; } + public int ParameterIndex { get; set; } + public List TypeArguments { get; set; } + } + + private class MethodSession + { + public KnownType Type { get; set; } + public String MethodName { get; set; } + public int ParameterIndex { get; set; } + } + + private class DeclaredMethodSession + { + public ScriptType Type { get; set; } + public ScriptSymbol Method { get; set; } + } + + #endregion + + #region Properties + + /// + /// Gets or sets a value indicating whether to enable folding. + /// + public bool EnableFolding + { + get { return (bool)GetValue(EnableFoldingProperty); } + set { SetValue(EnableFoldingProperty, value); } + } + public static readonly DependencyProperty EnableFoldingProperty = + DependencyProperty.Register("EnableFolding", typeof(bool), typeof(ScriptEditor), new PropertyMetadata(true, (d, e) => (d as ScriptEditor).OnEnableFoldingChanged())); + + /// + /// + /// + public RelayCommand IndentCommand + { + get { return (RelayCommand)GetValue(IndentCommandProperty); } + set { SetValue(IndentCommandProperty, value); } + } + public static readonly DependencyProperty IndentCommandProperty = + DependencyProperty.Register("IndentCommand", typeof(RelayCommand), typeof(ScriptEditor), new PropertyMetadata(null)); + + /// + /// Gets or sets the reference assemblies. + /// + public ObservableCollection ReferenceAssemblies + { + get { return (ObservableCollection)GetValue(ReferenceAssembliesProperty); } + set { SetValue(ReferenceAssembliesProperty, value); } + } + public static readonly DependencyProperty ReferenceAssembliesProperty = + DependencyProperty.Register("ReferenceAssemblies", typeof(ObservableCollection), typeof(ScriptEditor), new PropertyMetadata(null)); + + public Object CurrentPopupContent + { + get { return (Object)GetValue(CurrentPopupContentProperty); } + set { SetValue(CurrentPopupContentProperty, value); } + } + public static readonly DependencyProperty CurrentPopupContentProperty = + DependencyProperty.Register("CurrentPopupContent", typeof(Object), typeof(ScriptEditor), new PropertyMetadata(null)); + + + #endregion + + #region Constructors + + /// + /// Initializes the class. + /// + static ScriptEditor() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ScriptEditor), new FrameworkPropertyMetadata(typeof(ScriptEditor))); + } + + /// + /// Initializes a new instance of the class. + /// + public ScriptEditor() + { + _declaredTypes = new List(); + + _current_usings = new List(); + + ReferenceAssemblies = new ObservableCollection(); + + //Add basic assemblies... + ReferenceAssemblies.Add(new ReferenceAssembly(typeof(String))); //mscorelib + ReferenceAssemblies.Add(new ReferenceAssembly(typeof(Enumerable))); //System.Core + ReferenceAssemblies.Add(new ReferenceAssembly(typeof(Tango.Core.CoreSettings))); //System.Core + + _knownTypes = new List(); + _parser = new ScriptParser(); + + TextArea.IndentationStrategy = new Indentation.CSharp.CSharpIndentationStrategy(Options); + foldingStrategy = new BraceFoldingStrategy(); + + _update_timer = new DispatcherTimer(); + _update_timer.Interval = TimeSpan.FromSeconds(2); + _update_timer.Tick += UpdateTimer_Tick; + _update_timer.Start(); + + IndentCommand = new RelayCommand(IndentCode); + + TextArea.TextEntered += TextArea_TextEntered; + + completionWindow = new CompletionWindow(TextArea); + completionWindow.WindowStyle = WindowStyle.None; + completionWindow.AllowsTransparency = true; + completionWindow.ResizeMode = ResizeMode.NoResize; + completionWindow.InsertionRequest += CompletionWindow_InsertionRequest; + } + + #endregion + + #region Update Time Tick + + /// + /// Handles the Tick event of the UpdateTimer control. + /// + /// The source of the event. + /// The instance containing the event data. + private void UpdateTimer_Tick(object sender, EventArgs e) + { + InvalidateFolding(); + InvalidateUsings(); + InvalidateScriptTypesHighlightings(); + } + + #endregion + + #region Properties Changes + + private void OnEnableFoldingChanged() + { + if (EnableFolding) + { + foldingManager = FoldingManager.Install(TextArea); + } + else + { + FoldingManager.Uninstall(foldingManager); + } + } + + #endregion + + #region Override Methods + + /// + /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. + /// + /// The that contains the event data. + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + if (CurrentPopupContent is MethodPopup && (e.Key == Key.Down || e.Key == Key.Up)) + { + e.Handled = true; + + if (e.Key == Key.Down) + { + (CurrentPopupContent as MethodPopup).IncrementMethod(); + } + else if (e.Key == Key.Up) + { + (CurrentPopupContent as MethodPopup).DecrementMethod(); + } + + return; + } + + base.OnPreviewKeyDown(e); + HidePopup(); + HandleKeyCombinations(e); + } + + #endregion + + #region Apply Template + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _popup = GetTemplateChild("PART_popup") as Popup; + } + + #endregion + + #region Key Combination Handling + + /// + /// Handles the key combinations. + /// + /// The instance containing the event data. + private void HandleKeyCombinations(KeyEventArgs e) + { + if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.K) && Keyboard.IsKeyDown(Key.D)) + { + try + { + int index = CaretOffset; + Document.BeginUpdate(); + IndentCode(); + Document.EndUpdate(); + e.Handled = true; + CaretOffset = index; + } + catch + { + Debug.WriteLine("Error indenting code."); + } + } + else if (e.Key == Key.Oem2) + { + int offset = CaretOffset; + var line = Document.GetLineByOffset(offset); + + String text = GetCurrentLineText(); + if (text.TrimStart('\t', ' ').StartsWith("//")) + { + Document.BeginUpdate(); + Document.Replace(line, "/// \n/// \n/// "); + Document.EndUpdate(); + e.Handled = true; + CaretOffset = Document.GetLineByNumber(line.LineNumber + 1).EndOffset; + } + } + else if (e.Key == Key.End || e.Key == Key.Home) + { + HideCompletionWindow(); + } + } + + #endregion + + #region Text Entered + + private void TextArea_TextEntered(object sender, TextCompositionEventArgs e) + { + List items = new List(); + + HidePopup(); + + var lineText = GetCurrentLineText(); + var previousWords = GetPreviousWords(); + var previousWordsLast = previousWords.LastOrDefault(); + String currentWord = previousWordsLast != null ? previousWordsLast.Replace("\t", "") : String.Empty; + String currentWordIncludingParenthesis = currentWord.Split('(').LastOrDefault(); + + if (previousWords.Count > 0 && previousWords.First().Trim().StartsWith("//")) return; + + if (e.Text == " " && previousWords.Count > 2 && previousWords[previousWords.Count - 2] == "=") + { + var expression = previousWords.First(); + var knownType = GetKnownTypeFromExpression(expression + "."); + + if (knownType != null && knownType.Type.IsEnum) + { + completionWindow.HideCompletion(); + IList data = new List(); + + foreach (var field in knownType.Fields) + { + data.Add(new FieldCompletionItem() + { + Class = knownType.FriendlyName, + Name = knownType.FriendlyName + "." + field.Name, + Type = field.ReturnTypeFriendlyName, + Description = field.Summary, + }); + } + + ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord()); + } + + } + else if (e.Text == " " && GetPreviousWord() == "new") + { + var s = _parser.GetExpressionFirst(GetCurrentLineText()); + + if (s != null) + { + String type = s.Declaration.Type.ToString(); + + IList data = new List(); + + data.Add(new ClassCompletionItem() + { + Name = type, + Description = "Auto generate assignment...", + }); + + ShowCompletionWindow(data, type); + } + } + else if (e.Text == ";" || e.Text == " ") + { + HideCompletionWindow(); + } + else if (e.Text == ".") + { + var knownType = GetCurrentKnownType(); + + if (knownType != null) + { + completionWindow.HideCompletion(); + IList data = new List(); + + if (!knownType.Type.IsEnum) + { + var typeMembers = knownType.Members.ToList(); + + foreach (var methodGroup in typeMembers.OfType().GroupBy(x => x.NameWithTypeArguments)) + { + var method = methodGroup.First(); + + data.Add(new MethodCompletionItem() + { + Class = knownType.FriendlyName, + Name = method.NameWithTypeArguments, + ReturnType = method.ReturnTypeFriendlyName, + Description = method.Summary, + Parameters = method.Parameters, + Overloads = methodGroup.Count() - 1, + }); + } + + foreach (var methodGroup in typeMembers.Where(x => x.GetType() != typeof(KnownTypeMethod)).GroupBy(x => x.Name)) + { + var member = methodGroup.First(); + + data.Add(new PropertyCompletionItem() + { + Class = knownType.FriendlyName, + Name = member.Name, + Type = member.ReturnTypeFriendlyName, + Description = member.Summary, + }); + + } + } + else + { + foreach (var field in knownType.Fields) + { + data.Add(new FieldCompletionItem() + { + Class = knownType.FriendlyName, + Name = field.Name, + Type = field.ReturnTypeFriendlyName, + Description = field.Summary, + }); + + } + } + + ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord()); + } + else + { + var declaredType = GetCurrentDeclaredType(); + + if (declaredType != null) + { + completionWindow.HideCompletion(); + IList data = new List(); + + var typeMembers = declaredType.Symbols.ToList(); + + foreach (var methodGroup in typeMembers.GroupBy(x => x.Name)) + { + var member = methodGroup.First(); + + if (member.Kind == SymbolKind.Method) + { + var methodCompletion = new MethodCompletionItem() + { + Class = declaredType.Name, + Name = member.Name, + ReturnType = member.Type, + Description = member.Summary, + Overloads = methodGroup.Count() - 1, + }; + + + for (int i = 0; i < member.Parameters.Count; i++) + { + var pair = member.Parameters[i]; + + methodCompletion.Parameters.Add(new KnownTypeMethodParameter() + { + Type = pair.Key, + Name = pair.Value, + IsLast = (i == member.Parameters.Count - 1) + }); + } + + data.Add(methodCompletion); + + } + else if (member.Kind == SymbolKind.Property) + { + data.Add(new PropertyCompletionItem() + { + Class = declaredType.Name, + Name = member.Name, + Type = member.Type, + Description = member.Summary, + }); + } + else if (member.Kind == SymbolKind.Field) + { + data.Add(new FieldCompletionItem() + { + Class = declaredType.Name, + Name = member.Name, + Type = member.Type, + Description = member.Summary, + }); + } + } + + ShowCompletionWindow(data, GetCurrentWord()); + } + } + } + else if (e.Text == "(" || e.Text == ",") + { + completionWindow.HideCompletion(); + + try + { + var session = GetConstructionSession(); + + if (session != null) + { + var content = CreateConstructionSessionPopupContent(session); + if (content.Methods.Count > 0) + { + ShowPopup(content); + return; + } + } + + var methodSession = GetMethodSession(); + + if (methodSession != null) + { + var content = CreateMethodSessionPopupContent(methodSession); + if (content.Methods.Count > 0) + { + ShowPopup(content); + return; + } + } + + var declaredMethodSession = GetDeclaredMethodSession(); + + if (declaredMethodSession != null) + { + var content = CreateDeclaredMethodSessionPopupContent(declaredMethodSession); + if (content.Methods.Count > 0) + { + ShowPopup(content); + return; + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + else if (lineText.StartsWith("using")) + { + if (completionWindow.IsVisible) + { + completionWindow.UpdatePositionFix(); + return; + } + + IList data = new List(); + + foreach (var asm in ReferenceAssemblies) + { + foreach (var ns in asm.Assembly.GetTypes().Select(x => x.Namespace).Distinct().Where(x => x != null)) + { + data.Add(new NamespaceCompletionItem() + { + Name = ns, + Assembly = asm.Assembly.GetName().Name, + }); + } + } + + data = data.DistinctBy(x => x.Text).ToList(); + + ShowCompletionWindow(data, GetCurrentWord()); + } + else if (e.Text == "{") + { + int parentesisCount = lineText.TakeWhile(x => x != '{').Count(x => x == '\"'); + + if (parentesisCount % 2 == 0) + { + Document.Insert(CaretOffset, "}"); + CaretOffset--; + } + } + else if (e.Text == "}") + { + if (Document.GetText(CaretOffset - 2, 1) == "{" && Document.GetText(CaretOffset, 1) == "}") + { + Document.Replace(CaretOffset, 1, ""); + } + } + else if (e.Text == "\n") + { + if (Document.GetText(CaretOffset - 3, 1) == "{" && Document.GetText(CaretOffset, 1) == "}") + { + CaretOffset--; + Document.Insert(CaretOffset, "\n\t"); + } + } + else if (!currentWordIncludingParenthesis.Contains(".") || currentWord[currentWord.Length - 2] == '<') + { + if (completionWindow.IsVisible) + { + completionWindow.UpdatePositionFix(); + return; + } + + var previous_word = GetPreviousWord(); + var word = GetCurrentWord(); + + if (word.Contains("<")) + { + word = word.Last(x => x != '<').ToString(); + } + + if (previous_word != word) + { + if (_knownTypes.Exists(x => x.Name == previous_word)) + { + return; + } + + if (_blocking_type_words.Contains(previous_word)) + { + return; + } + } + + if (!String.IsNullOrWhiteSpace(word)) + { + IList data = new List(); + + foreach (var type in _declaredTypes.Where(x => x.Name.StartsWith(word))) + { + if (type.Kind == TypeKind.Struct) + { + data.Add(new StructCompletionItem() + { + Name = type.Name, + Description = type.Summary, + Namespace = type.ContainingNamespace, + Priority = 1, + }); + } + else if (type.Kind == TypeKind.Enum) + { + data.Add(new EnumCompletionItem() + { + Name = type.Name, + Description = type.Summary, + Namespace = type.ContainingNamespace, + Priority = 1, + }); + } + else if (type.Kind == TypeKind.Interface) + { + data.Add(new InterfaceCompletionItem() + { + Name = type.Name, + Description = type.Summary, + Namespace = type.ContainingNamespace, + Priority = 1, + }); + } + else if (type.Kind == TypeKind.Class) + { + data.Add(new ClassCompletionItem() + { + Name = type.Name, + Description = type.Summary, + Namespace = type.ContainingNamespace, + Priority = 1, + }); + } + else + { + throw new NotImplementedException("Implement generic item here!"); + } + } + + foreach (var type in _knownTypes.ToList().Where(x => x.Name.StartsWith(word))) + { + if (type.Type.IsEnum) + { + data.Add(new EnumCompletionItem() + { + Namespace = type.Type.Namespace, + Description = type.Summary, + Name = type.FriendlyName, + Priority = 0, + }); + } + else if (type.Type.IsInterface) + { + data.Add(new InterfaceCompletionItem() + { + Name = type.FriendlyName, + Description = type.Summary, + Namespace = type.Type.Namespace, + Priority = 0, + }); + } + else if (type.Type.IsValueType) + { + data.Add(new StructCompletionItem() + { + Name = type.FriendlyName, + Description = type.Summary, + Namespace = type.Type.Namespace, + Priority = 0, + }); + } + else if (type.Type.IsClass) + { + data.Add(new ClassCompletionItem() + { + Name = type.FriendlyName, + Description = type.Summary, + Namespace = type.Type.Namespace, + Priority = 0, + }); + } + else + { + throw new NotImplementedException("Implement generic item here."); + } + } + + foreach (var symbol in _parser.GetContextSymbols(Document.Text, CaretOffset).Where(x => x.Name.StartsWith(GetCurrentWord()))) + { + if (symbol.Kind == SymbolKind.Property) + { + data.Add(new PropertyCompletionItem() + { + Class = symbol.Class, + Description = symbol.Summary, + Name = symbol.Name, + Type = symbol.Type, + Priority = 2, + }); + } + else if (symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter) + { + data.Add(new FieldCompletionItem() + { + Class = symbol.Class, + Description = symbol.Summary, + Name = symbol.Name, + Type = symbol.Type, + Priority = 2, + }); + } + else if (symbol.Kind == SymbolKind.Method) + { + var methodCompletion = new MethodCompletionItem() + { + Class = symbol.Class, + Description = symbol.Summary, + Name = symbol.Name, + ReturnType = symbol.Type, + Priority = 2, + }; + + for (int i = 0; i < symbol.Parameters.Count; i++) + { + var pair = symbol.Parameters[i]; + + methodCompletion.Parameters.Add(new KnownTypeMethodParameter() + { + Type = pair.Key, + Name = pair.Value, + IsLast = (i == symbol.Parameters.Count - 1) + }); + } + + data.Add(methodCompletion); + } + } + + ShowCompletionWindow(data, word); + } + } + } + + #endregion + + #region Completion Window Insertion + + private void CompletionWindow_InsertionRequest(ICompletionData item) + { + item.Complete(this); + } + + #endregion + + #region Private/Internal Methods + + private void ShowCompletionWindow(IList suggestions, String filter) + { + IList data = completionWindow.CompletionList.CompletionData; + data.Clear(); + + foreach (var item in suggestions) + { + data.Add(item); + } + + if (data.Count > 0) + { + completionWindow.ShowCompletion(); + + if (completionWindow.CompletionList.ListBox != null) + { + completionWindow.CompletionList.SelectItemFiltering(filter); + } + } + else + { + completionWindow.HideCompletion(); + } + } + + private void HideCompletionWindow() + { + completionWindow.HideCompletion(); + } + + private List GetScriptClassNames() + { + List list = new List(); + + Regex r = new Regex(@"(class\s+)(\w+)"); + var matches = r.Matches(Text); + + foreach (var m in matches.OfType()) + { + var g = m.Groups.OfType().Last(); + list.Add(g.Value); + } + + return list.Distinct().ToList(); + } + + private List GetScriptInterfaceOfEnumNames() + { + List list = new List(); + + Regex r = new Regex(@"((enum|interface)\s+)(\w+)"); + var matches = r.Matches(Text); + + foreach (var m in matches.OfType()) + { + var g = m.Groups.OfType().Last(); + list.Add(g.Value); + } + + return list.Distinct().ToList(); + } + + private List GetScriptEnumsAndInterfaces() + { + List list = new List(); + + Regex r = new Regex(@"((public|private|internal)\s+(enum|interface)\s+\w+)"); + var matches = r.Matches(Text); + + foreach (var m in matches) + { + + } + + return list; + } + + private KnownType GetCurrentKnownType() + { + var expression = GetPreviousWords().LastOrDefault(); + return GetKnownTypeFromExpression(expression); + } + + private KnownType GetKnownTypeFromExpression(String expression) + { + if (expression != null) + { + var tree = expression.Split('.').Select(x => x.Remove(@"\n|\t|\r|\(.*\)|\[.*\]|\s")).ToList(); + var variableName = tree.FirstOrDefault(); + + if (variableName != null) + { + //Search for enum type first + var enumType = _knownTypes.FirstOrDefault(x => x.Name == variableName && x.Type.IsEnum); + + if (enumType != null) + { + return enumType; + } + + tree.RemoveAt(0); + var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); + var variable = variables.FirstOrDefault(x => x.Name == variableName); + + if (variable != null) + { + var knownType = _knownTypes.FirstOrDefault(x => x.FriendlyName == Regex.Replace(variable.Type, "<.+>", "")); + + if (knownType != null) + { + while (tree.Count > 1) + { + var memberName = tree.First(); + tree.RemoveAt(0); + var member = knownType.Members.FirstOrDefault(x => x.Name == memberName); + + if (member == null) + { + return null; + } + + knownType = _knownTypes.FirstOrDefault(x => x.Type.Namespace + "." + x.Type.Name == member.ReturnType.Namespace + "." + member.ReturnType.Name); + } + + return knownType; + } + } + } + } + + return null; + } + + private ScriptType GetCurrentDeclaredType() + { + var expression = GetPreviousWords().LastOrDefault(); + + if (expression != null) + { + var tree = expression.Split('.').Select(x => x.Remove(@"\n|\t|\r|\(.*\)|\[.*\]|\s")).ToList(); + var variableName = tree.FirstOrDefault(); + + if (variableName != null) + { + tree.RemoveAt(0); + var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); + var variable = variables.FirstOrDefault(x => x.Name == variableName); + + if (variable != null) + { + var declaredType = _declaredTypes.FirstOrDefault(x => x.Name == Regex.Replace(variable.Type, "<.+>", "")); + + if (declaredType != null) + { + while (tree.Count > 1) + { + var memberName = tree.First(); + tree.RemoveAt(0); + var member = declaredType.Symbols.FirstOrDefault(x => x.Name == memberName); + + if (member == null) + { + return null; + } + + declaredType = _declaredTypes.FirstOrDefault(x => x.ContainingNamespace + "." + x.Name == member.ContainingNamespace + "." + member.Type); + } + + return declaredType; + } + } + } + } + + return null; + } + + private void ShowPopup(Object content) + { + var position = TextArea.Caret.Position; + var textView = TextArea.TextView; + + var visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom); + var visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop); + + Point location = textView.PointToScreen(visualLocation - textView.ScrollOffset); + Point locationTop = textView.PointToScreen(visualLocationTop - textView.ScrollOffset); + + _popup.Placement = PlacementMode.Absolute; + _popup.PlacementRectangle = new Rect(location, new Size(200, 100)); + + CurrentPopupContent = content; + + _popup.IsOpen = true; + } + + private void HidePopup() + { + _popup.IsOpen = false; + CurrentPopupContent = null; + } + + private MethodPopup CreateConstructionSessionPopupContent(ConstructionSession session) + { + MethodPopup popup = new MethodPopup(); + + foreach (var c in session.Type.Constructors) + { + MethodDescription method = new MethodDescription(); + method.ReturnType = session.Type.Name; + method.Description = c.Summary; + + if (session.Type.Type.IsGenericType && session.TypeArguments != null) + { + method.ReturnType = new String(session.Type.Name.TakeWhile(x => x != '`').ToArray()) + $"<{String.Join(",", session.TypeArguments)}>"; + } + + var parameters = c.Parameters; + + foreach (var p in parameters) + { + ParameterDescription pDescription = new ParameterDescription(method); + pDescription.Name = p.Name; + pDescription.Type = p.Type; + pDescription.Description = p.Description; + method.Parameters.Add(pDescription); + } + + popup.Methods.Add(method); + } + + if (session.ParameterIndex > 0) + { + popup.CurrentMethod = popup.Methods.FirstOrDefault(x => x.Parameters.Count == session.ParameterIndex + 1); + + if (popup.CurrentMethod == null) + { + popup.CurrentMethod = popup.Methods.FirstOrDefault(); + } + } + else + { + popup.CurrentMethod = popup.Methods.FirstOrDefault(); + } + + if (popup.CurrentMethod != null) + { + popup.CurrentMethodIndex = popup.Methods.IndexOf(popup.CurrentMethod) + 1; + } + + return popup; + } + + private MethodPopup CreateMethodSessionPopupContent(MethodSession session) + { + MethodPopup popup = new MethodPopup(); + + foreach (var m in session.Type.Methods.Where(x => x.Name == session.MethodName)) + { + MethodDescription method = new MethodDescription(); + method.ReturnType = session.Type.Name; + method.Description = m.Summary; + method.Name = m.NameWithTypeArguments; + method.Class = session.Type.FriendlyName; + + //if (session.Type.Type.IsGenericType && session.TypeArguments != null) + //{ + // method.ReturnType = new String(session.Type.Name.TakeWhile(x => x != '`').ToArray()) + $"<{String.Join(",", session.TypeArguments)}>"; + //} + + var parameters = m.Parameters; + + foreach (var p in parameters) + { + ParameterDescription pDescription = new ParameterDescription(method); + pDescription.Name = p.Name; + pDescription.Type = p.Type; + pDescription.Description = p.Description; + method.Parameters.Add(pDescription); + } + + popup.Methods.Add(method); + } + + if (session.ParameterIndex > 0) + { + popup.CurrentMethod = popup.Methods.FirstOrDefault(x => x.Parameters.Count == session.ParameterIndex + 1); + + if (popup.CurrentMethod == null) + { + popup.CurrentMethod = popup.Methods.FirstOrDefault(); + } + } + else + { + popup.CurrentMethod = popup.Methods.FirstOrDefault(); + } + + if (popup.CurrentMethod != null) + { + popup.CurrentMethodIndex = popup.Methods.IndexOf(popup.CurrentMethod) + 1; + } + + return popup; + } + + private MethodPopup CreateDeclaredMethodSessionPopupContent(DeclaredMethodSession session) + { + MethodPopup popup = new MethodPopup(); + + MethodDescription method = new MethodDescription(); + method.ReturnType = session.Method.Type; + method.Description = session.Method.Summary; + method.Name = session.Method.Name; + method.Class = session.Method.Class; + + //if (session.Type.Type.IsGenericType && session.TypeArguments != null) + //{ + // method.ReturnType = new String(session.Type.Name.TakeWhile(x => x != '`').ToArray()) + $"<{String.Join(",", session.TypeArguments)}>"; + //} + + foreach (var p in session.Method.Parameters) + { + ParameterDescription pDescription = new ParameterDescription(method); + pDescription.Type = p.Key; + pDescription.Name = p.Value; + method.Parameters.Add(pDescription); + } + + popup.Methods.Add(method); + + //if (false) + //{ + // popup.CurrentMethod = popup.Methods.FirstOrDefault(x => x.Parameters.Count == session.ParameterIndex + 1); + + // if (popup.CurrentMethod == null) + // { + // popup.CurrentMethod = popup.Methods.FirstOrDefault(); + // } + //} + //else + //{ + popup.CurrentMethod = popup.Methods.FirstOrDefault(); + //} + + if (popup.CurrentMethod != null) + { + popup.CurrentMethodIndex = popup.Methods.IndexOf(popup.CurrentMethod) + 1; + } + + return popup; + } + + private void InvalidateHighlighting() + { + if (!_isLoadingTypes) + { + _isLoadingTypes = true; + _knownTypes.Clear(); + + var assemblies = ReferenceAssemblies.ToList(); + var usings = _current_usings.ToList(); + + Thread t = new Thread(() => + { + foreach (var asm in assemblies.Select(x => x.Assembly)) + { + Parallel.ForEach(asm.GetTypes().Where(x => x.IsVisible && x.IsPublic && !x.IsPrimitive), (type) => + { + if (usings.Exists(x => type.Namespace == x)) + { + lock (_knownTypes) + { + if (!_knownTypes.Exists(x => x.Type.FullName == type.FullName)) + { + _knownTypes.Add(new KnownType(type)); + } + } + } + }); + } + + if (_knownTypes.Count > 0 || _declaredTypes.Count > 0) + { + String text = String.Empty; + + Stream xshd_stream = typeof(ScriptEditor).Assembly.GetManifestResourceStream("Tango.Scripting.Editors.Highlighting.Resources.CSharp-Mode.xshd"); + + using (StreamReader reader = new StreamReader(xshd_stream)) + { + text = reader.ReadToEnd(); + } + + List referenceTypes = new List(); + List interfaceTypes = new List(); + + lock (_knownTypes) + { + foreach (var type in _knownTypes.ToList().Where(x => x != null)) + { + String name = type.Name; + + if (type.Type.ContainsGenericParameters) + { + name = new String(name.TakeWhile(x => x != '`').ToArray()); + } + + if (type.Type.IsInterface || type.Type.IsEnum) + { + interfaceTypes.Add(String.Format("{0}", name)); + } + else if (type.Type.IsClass || (type.Type.IsValueType)) + { + referenceTypes.Add(String.Format("{0}", name)); + } + } + } + + foreach (var type in _declaredTypes) + { + if (type.Kind == TypeKind.Interface || type.Kind == TypeKind.Enum) + { + interfaceTypes.Add(String.Format("{0}", type.Name)); + } + else if (type.Kind == TypeKind.Class) + { + referenceTypes.Add(String.Format("{0}", type.Name)); + } + } + + if (referenceTypes.Count > 0) + { + text = text.Replace("@ReferenceTypes@", String.Join(Environment.NewLine, referenceTypes.Distinct())); + } + + if (interfaceTypes.Count > 0) + { + text = text.Replace("@InterfaceTypes@", String.Join(Environment.NewLine, interfaceTypes.Distinct())); + } + + MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)); + + XmlTextReader xshd_reader = new XmlTextReader(ms); + + Dispatcher.BeginInvoke(new Action(() => + { + SyntaxHighlighting = HighlightingLoader.Load(xshd_reader, HighlightingManager.Instance); + xshd_reader.Close(); + ms.Dispose(); + })); + + + foreach (var knownType in _knownTypes) + { + knownType.LoadDocumentation(); + } + } + + _isLoadingTypes = false; + }); + t.IsBackground = true; + t.Start(); + } + } + + private void InvalidateScriptTypesHighlightings() + { + var declaredTypes = _parser.GetDeclaredTypes(Text); + + if (declaredTypes.Exists(x => !_declaredTypes.Exists(y => y.Name == x.Name)) || _declaredTypes.Exists(x => !declaredTypes.Exists(y => y.Name == x.Name))) + { + _declaredTypes = declaredTypes; + InvalidateHighlighting(); + } + + _declaredTypes = declaredTypes; + + //for (int i = 0; i < TextArea.TextView.LineTransformers.Count; i++) + //{ + // if (TextArea.TextView.LineTransformers[i] is OffsetColorizer) + // { + // TextArea.TextView.LineTransformers.RemoveAt(i); + // } + //} + + //foreach (var cls in scriptClasses) + //{ + // Document.BeginUpdate(); + + // var line = Document.GetLineByOffset(cls.Index); + + // OffsetColorizer colorizer = new OffsetColorizer(line, cls.Index, cls.Index + cls.Name.Length, Brushes.Red); + // TextArea.TextView.LineTransformers.Add(colorizer); + + // Document.EndUpdate(); + //} + } + + private void InvalidateUsings() + { + var oldUsings = _current_usings.ToList(); + _current_usings = _parser.GetUsings(Text); + + if (_current_usings.Exists(x => !oldUsings.Exists(y => y == x)) || oldUsings.Exists(x => !_current_usings.Exists(y => y == x))) + { + InvalidateHighlighting(); + } + } + + private void InvalidateFolding() + { + if (EnableFolding) + { + if (foldingManager == null) + { + foldingManager = FoldingManager.Install(TextArea); + } + + foldingStrategy.UpdateFoldings(foldingManager, Document); + } + } + + private void IndentCode() + { + Text = Indentation.CSharp.CSharpIndentationHelper.IndentCSharpCode(Text); + //Text = _parser.IndentCSharpCode(Text); + } + + internal DocumentLine GetCurrentLine() + { + int offset = CaretOffset; + var line = Document.GetLineByOffset(offset); + return line; + } + + private String GetCurrentLineText() + { + var text = Document.GetText(GetCurrentLine()); + return text; + } + + internal String GetCurrentWord() + { + return GetWordByEndIndex(CaretOffset); + } + + private String GetWordByEndIndex(int index) + { + String word = String.Empty; + var line = GetCurrentLine(); + + int position = index; + + for (int i = position - 1; i >= line.Offset; i--) + { + char c = Document.GetText(i, 1).First(); + + if (word_separators.Contains(c)) + { + break; + } + + word += c; + } + + word = new string(word.Reverse().ToArray()); + + if (word.Length > 0) + { + word = word.Replace(".", ""); + } + + return word; + } + + internal int GetCurrentWordStartIndex() + { + var line = GetCurrentLine(); + + int position = CaretOffset; + + for (int i = position - 1; i >= line.Offset; i--) + { + char c = Document.GetText(i, 1).First(); + + if (word_separators.Contains(c)) + { + return i + 1; + } + } + + return line.Offset; + } + + private String GetPreviousWord() + { + int index = GetCurrentWordStartIndex() - 1; + return GetWordByEndIndex(index); + } + + private ConstructionSession GetConstructionSession() + { + var expression = _parser.GetCurrentConstructionExpression(GetCurrentLineText()); + + if (expression != null) + { + ConstructionSession session = new ConstructionSession(); + + var line = GetCurrentLine(); + int parameterIndex = 0; + for (int i = CaretOffset; i > line.Offset; i--) + { + String c = Document.GetText(i, 1); + + if (c == "(") + { + var typeDeclaration = expression.Type as GenericNameSyntax; + + KnownType type = null; + + if (typeDeclaration != null) + { + var typeName = typeDeclaration.Identifier.ToString(); + var argumentsCount = typeDeclaration.TypeArgumentList.Arguments.Count; + session.TypeArguments = typeDeclaration.TypeArgumentList.Arguments.Select(x => x.ToString()).ToList(); + + if (argumentsCount == 0) + { + type = _knownTypes.FirstOrDefault(x => x.Type.Name == expression.Type.ToString()); + } + else + { + type = _knownTypes.FirstOrDefault(x => x.Type.Name == typeName + "`" + argumentsCount); + } + } + else + { + type = _knownTypes.FirstOrDefault(x => x.Name == expression.Type.ToString()); + } + + if (type != null) + { + session.Type = type; + session.ParameterIndex = parameterIndex; + return session; + } + else + { + return null; + } + } + else if (c == ",") + { + parameterIndex++; + } + + } + } + + return null; + } + + private MethodSession GetMethodSession() + { + var words = GetCurrentLineText().Split(' '); + + if (words.Count() > 0 && (words.First() == "private" || words.First() == "public" || words.First() == "void")) + { + return null; + } + + var expression = words.LastOrDefault(); + + if (expression != null) + { + int parameterIndex = expression.Count(x => x == ','); + expression = new string(expression.TakeWhile(x => x != '(').ToArray()); + + var tree = expression.Split('.').Select(x => x.Remove(@"\n|\r|\s|\t|\(|\)|\[|\]|<.*>")).ToList(); + var variableName = tree.FirstOrDefault(); + + if (variableName != null && tree.Count > 1) + { + tree.RemoveAt(0); + var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); + var variable = variables.FirstOrDefault(x => x.Name == variableName); + + if (variable != null) + { + var knownType = _knownTypes.FirstOrDefault(x => x.FriendlyName == Regex.Replace(variable.Type, "<.+>", "")); + + if (knownType != null) + { + while (tree.Count > 1) + { + var memberName = tree.First(); + tree.RemoveAt(0); + var member = knownType.Members.FirstOrDefault(x => x.Name == memberName); + + if (member == null) + { + return null; + } + + knownType = _knownTypes.FirstOrDefault(x => x.Type.Namespace + "." + x.Type.Name == member.ReturnType.Namespace + "." + member.ReturnType.Name); + } + + return new MethodSession() + { + Type = knownType, + MethodName = tree.Last(), + ParameterIndex = parameterIndex, + }; + } + } + } + } + + return null; + } + + private DeclaredMethodSession GetDeclaredMethodSession() + { + var words = GetCurrentLineText().Split(' '); + + if (words.Count() > 0 && (words.First() == "private" || words.First() == "public" || words.First() == "void")) + { + return null; + } + + var expression = GetPreviousWords().LastOrDefault(); + + if (expression != null) + { + var tree = expression.Split('.').Select(x => x.Replace("\n", "").Replace("\r", "").Replace(" ", "").Replace("\t", "").Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "")).ToList(); + var variableName = tree.FirstOrDefault(); + + if (variableName != null && tree.Count > 0) + { + tree.RemoveAt(0); + var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); + var variable = variables.FirstOrDefault(x => x.Name == variableName); + + if (variable != null) + { + var declaredType = _declaredTypes.FirstOrDefault(x => x.Name == Regex.Replace(variable.Class, "<.+>", "")); + + if (declaredType != null) + { + while (tree.Count > 1) + { + var memberName = tree.First(); + tree.RemoveAt(0); + var member = declaredType.Symbols.FirstOrDefault(x => x.Name == memberName); + + if (member == null) + { + return null; + } + + declaredType = _declaredTypes.FirstOrDefault(x => x.ContainingNamespace + "." + x.Name == member.ContainingNamespace + "." + member.Type); + } + + var methodName = tree.Count > 0 ? tree.Last() : variableName; + + var method = declaredType.Symbols.FirstOrDefault(x => x.Kind == SymbolKind.Method && x.Name == methodName); + + if (method != null) + { + return new DeclaredMethodSession() + { + Type = declaredType, + Method = method, + }; + } + } + else if (tree.Count == 0) + { + var method = variable; + + if (method != null) + { + return new DeclaredMethodSession() + { + Type = declaredType, + Method = method, + }; + } + } + } + } + } + + return null; + } + + private List GetPreviousWords() + { + var currentLine = GetCurrentLine(); + var currentText = Document.GetText(currentLine.Offset, CaretOffset - currentLine.Offset); + return currentText.Split(' ', ',').ToList(); + } + + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs new file mode 100644 index 000000000..dca5bea30 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.cs @@ -0,0 +1,60 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +namespace Tango.Scripting.Editors.Search +{ + /// + /// A button that opens a drop-down menu when clicked. + /// + class DropDownButton : ButtonBase + { + public static readonly DependencyProperty DropDownContentProperty + = DependencyProperty.Register("DropDownContent", typeof(Popup), + typeof(DropDownButton), new FrameworkPropertyMetadata(null)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + protected static readonly DependencyPropertyKey IsDropDownContentOpenPropertyKey + = DependencyProperty.RegisterReadOnly("IsDropDownContentOpen", typeof(bool), + typeof(DropDownButton), new FrameworkPropertyMetadata(false)); + + public static readonly DependencyProperty IsDropDownContentOpenProperty = IsDropDownContentOpenPropertyKey.DependencyProperty; + + static DropDownButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); + } + + public Popup DropDownContent { + get { return (Popup)GetValue(DropDownContentProperty); } + set { SetValue(DropDownContentProperty, value); } + } + + public bool IsDropDownContentOpen { + get { return (bool)GetValue(IsDropDownContentOpenProperty); } + protected set { SetValue(IsDropDownContentOpenPropertyKey, value); } + } + + protected override void OnClick() + { + if (DropDownContent != null && !IsDropDownContentOpen) { + DropDownContent.Placement = PlacementMode.Bottom; + DropDownContent.PlacementTarget = this; + DropDownContent.IsOpen = true; + DropDownContent.Closed += DropDownContent_Closed; + this.IsDropDownContentOpen = true; + } + } + + void DropDownContent_Closed(object sender, EventArgs e) + { + ((Popup)sender).Closed -= DropDownContent_Closed; + this.IsDropDownContentOpen = false; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml new file mode 100644 index 000000000..8c7649c00 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/DropDownButton.xaml @@ -0,0 +1,71 @@ + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs new file mode 100644 index 000000000..dd4942f33 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/ISearchStrategy.cs @@ -0,0 +1,88 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Search +{ + /// + /// Basic interface for search algorithms. + /// + public interface ISearchStrategy : IEquatable + { + /// + /// Finds all matches in the given ITextSource and the given range. + /// + /// + /// This method must be implemented thread-safe. + /// All segments in the result must be within the given range, and they must be returned in order + /// (e.g. if two results are returned, EndOffset of first result must be less than or equal StartOffset of second result). + /// + IEnumerable FindAll(ITextSource document, int offset, int length); + + /// + /// Finds the next match in the given ITextSource and the given range. + /// + /// This method must be implemented thread-safe. + ISearchResult FindNext(ITextSource document, int offset, int length); + } + + /// + /// Represents a search result. + /// + public interface ISearchResult : ISegment + { + /// + /// Replaces parts of the replacement string with parts from the match. (e.g. $1) + /// + string ReplaceWith(string replacement); + } + + /// + /// Defines supported search modes. + /// + public enum SearchMode + { + /// + /// Standard search + /// + Normal, + /// + /// RegEx search + /// + RegEx, + /// + /// Wildcard search + /// + Wildcard + } + + /// + public class SearchPatternException : Exception, ISerializable + { + /// + public SearchPatternException() + { + } + + /// + public SearchPatternException(string message) : base(message) + { + } + + /// + public SearchPatternException(string message, Exception innerException) : base(message, innerException) + { + } + + // This constructor is needed for serialization. + /// + protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs new file mode 100644 index 000000000..cbe8c9bb0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/Localization.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; + +namespace Tango.Scripting.Editors.Search +{ + /// + /// Holds default texts for buttons and labels in the SearchPanel. Override properties to add other languages. + /// + public class Localization + { + /// + /// Default: 'Match case' + /// + public virtual string MatchCaseText { + get { return "Match case"; } + } + + /// + /// Default: 'Match whole words' + /// + public virtual string MatchWholeWordsText { + get { return "Match whole words"; } + } + + + /// + /// Default: 'Use regular expressions' + /// + public virtual string UseRegexText { + get { return "Use regular expressions"; } + } + + /// + /// Default: 'Find next (F3)' + /// + public virtual string FindNextText { + get { return "Find next (F3)"; } + } + + /// + /// Default: 'Find previous (Shift+F3)' + /// + public virtual string FindPreviousText { + get { return "Find previous (Shift+F3)"; } + } + + /// + /// Default: 'Error: ' + /// + public virtual string ErrorText { + get { return "Error: "; } + } + + /// + /// Default: 'No matches found!' + /// + public virtual string NoMatchesFoundText { + get { return "No matches found!"; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs new file mode 100644 index 000000000..e7ca7f8ce --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/RegexSearchStrategy.cs @@ -0,0 +1,70 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Documents; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Search +{ + class RegexSearchStrategy : ISearchStrategy + { + readonly Regex searchPattern; + readonly bool matchWholeWords; + + public RegexSearchStrategy(Regex searchPattern, bool matchWholeWords) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + this.searchPattern = searchPattern; + this.matchWholeWords = matchWholeWords; + } + + public IEnumerable FindAll(ITextSource document, int offset, int length) + { + int endOffset = offset + length; + foreach (Match result in searchPattern.Matches(document.Text)) { + int resultEndOffset = result.Length + result.Index; + if (offset > result.Index || endOffset < resultEndOffset) + continue; + if (matchWholeWords && (!IsWordBorder(document, result.Index) || !IsWordBorder(document, resultEndOffset))) + continue; + yield return new SearchResult { StartOffset = result.Index, Length = result.Length, Data = result }; + } + } + + static bool IsWordBorder(ITextSource document, int offset) + { + return TextUtilities.GetNextCaretPosition(document, offset - 1, LogicalDirection.Forward, CaretPositioningMode.WordBorder) == offset; + } + + public ISearchResult FindNext(ITextSource document, int offset, int length) + { + return FindAll(document, offset, length).FirstOrDefault(); + } + + public bool Equals(ISearchStrategy other) + { + var strategy = other as RegexSearchStrategy; + return strategy != null && + strategy.searchPattern.ToString() == searchPattern.ToString() && + strategy.searchPattern.Options == searchPattern.Options && + strategy.searchPattern.RightToLeft == searchPattern.RightToLeft; + } + } + + class SearchResult : TextSegment, ISearchResult + { + public Match Data { get; set; } + + public string ReplaceWith(string replacement) + { + return Data.Result(replacement); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs new file mode 100644 index 000000000..4bb102e21 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchCommands.cs @@ -0,0 +1,105 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Search +{ + /// + /// Search commands for AvalonEdit. + /// + public static class SearchCommands + { + /// + /// Finds the next occurrence in the file. + /// + public static readonly RoutedCommand FindNext = new RoutedCommand( + "FindNext", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3) } + ); + + /// + /// Finds the previous occurrence in the file. + /// + public static readonly RoutedCommand FindPrevious = new RoutedCommand( + "FindPrevious", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) } + ); + + /// + /// Closes the SearchPanel. + /// + public static readonly RoutedCommand CloseSearchPanel = new RoutedCommand( + "CloseSearchPanel", typeof(SearchPanel), + new InputGestureCollection { new KeyGesture(Key.Escape) } + ); + } + + /// + /// TextAreaInputHandler that registers all search-related commands. + /// + public class SearchInputHandler : TextAreaInputHandler + { + /// + /// Creates a new SearchInputHandler and registers the search-related commands. + /// + public SearchInputHandler(TextArea textArea) + : base(textArea) + { + RegisterCommands(this.CommandBindings); + panel = new SearchPanel(); + panel.Attach(TextArea); + } + + void RegisterCommands(ICollection commandBindings) + { + commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind)); + commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext)); + commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious)); + commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel)); + } + + SearchPanel panel; + + void ExecuteFind(object sender, ExecutedRoutedEventArgs e) + { + panel.Open(); + if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline)) + panel.SearchPattern = TextArea.Selection.GetText(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); }); + } + + void ExecuteFindNext(object sender, ExecutedRoutedEventArgs e) + { + panel.FindNext(); + } + + void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) + { + panel.FindPrevious(); + } + + void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) + { + panel.Close(); + } + + /// + /// Fired when SearchOptions are modified inside the SearchPanel. + /// + public event EventHandler SearchOptionsChanged { + add { panel.SearchOptionsChanged += value; } + remove { panel.SearchOptionsChanged -= value; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs new file mode 100644 index 000000000..b4af99f54 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.cs @@ -0,0 +1,467 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Folding; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Search +{ + /// + /// Provides search functionality for AvalonEdit. It is displayed in the top-right corner of the TextArea. + /// + public class SearchPanel : Control + { + TextArea textArea; + TextDocument currentDocument; + SearchResultBackgroundRenderer renderer; + TextBox searchTextBox; + SearchPanelAdorner adorner; + + #region DependencyProperties + /// + /// Dependency property for . + /// + public static readonly DependencyProperty UseRegexProperty = + DependencyProperty.Register("UseRegex", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should be interpreted as regular expression. + /// + public bool UseRegex { + get { return (bool)GetValue(UseRegexProperty); } + set { SetValue(UseRegexProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty MatchCaseProperty = + DependencyProperty.Register("MatchCase", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should be interpreted case-sensitive. + /// + public bool MatchCase { + get { return (bool)GetValue(MatchCaseProperty); } + set { SetValue(MatchCaseProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty WholeWordsProperty = + DependencyProperty.Register("WholeWords", typeof(bool), typeof(SearchPanel), + new FrameworkPropertyMetadata(false, SearchPatternChangedCallback)); + + /// + /// Gets/sets whether the search pattern should only match whole words. + /// + public bool WholeWords { + get { return (bool)GetValue(WholeWordsProperty); } + set { SetValue(WholeWordsProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty SearchPatternProperty = + DependencyProperty.Register("SearchPattern", typeof(string), typeof(SearchPanel), + new FrameworkPropertyMetadata("", SearchPatternChangedCallback)); + + /// + /// Gets/sets the search pattern. + /// + public string SearchPattern { + get { return (string)GetValue(SearchPatternProperty); } + set { SetValue(SearchPatternProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty MarkerBrushProperty = + DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(SearchPanel), + new FrameworkPropertyMetadata(Brushes.LightGreen, MarkerBrushChangedCallback)); + + /// + /// Gets/sets the Brush used for marking search results in the TextView. + /// + public Brush MarkerBrush { + get { return (Brush)GetValue(MarkerBrushProperty); } + set { SetValue(MarkerBrushProperty, value); } + } + + /// + /// Dependency property for . + /// + public static readonly DependencyProperty LocalizationProperty = + DependencyProperty.Register("Localization", typeof(Localization), typeof(SearchPanel), + new FrameworkPropertyMetadata(new Localization())); + + /// + /// Gets/sets the localization for the SearchPanel. + /// + public Localization Localization { + get { return (Localization)GetValue(LocalizationProperty); } + set { SetValue(LocalizationProperty, value); } + } + #endregion + + static void MarkerBrushChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.renderer.MarkerBrush = (Brush)e.NewValue; + } + } + + static SearchPanel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchPanel), new FrameworkPropertyMetadata(typeof(SearchPanel))); + } + + ISearchStrategy strategy; + + static void SearchPatternChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SearchPanel panel = d as SearchPanel; + if (panel != null) { + panel.ValidateSearchText(); + panel.UpdateSearch(); + } + } + + void UpdateSearch() + { + // only reset as long as there are results + // if no results are found, the "no matches found" message should not flicker. + // if results are found by the next run, the message will be hidden inside DoSearch ... + if (renderer.CurrentResults.Any()) + messageView.IsOpen = false; + strategy = SearchStrategyFactory.Create(SearchPattern ?? "", !MatchCase, WholeWords, UseRegex ? SearchMode.RegEx : SearchMode.Normal); + OnSearchOptionsChanged(new SearchOptionsChangedEventArgs(SearchPattern, MatchCase, UseRegex, WholeWords)); + DoSearch(true); + } + + /// + /// Creates a new SearchPanel. + /// + public SearchPanel() + { + } + + /// + /// Attaches this SearchPanel to a TextArea instance. + /// + public void Attach(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + adorner = new SearchPanelAdorner(textArea, this); + DataContext = this; + + renderer = new SearchResultBackgroundRenderer(); + currentDocument = textArea.Document; + if (currentDocument != null) + currentDocument.TextChanged += textArea_Document_TextChanged; + textArea.DocumentChanged += textArea_DocumentChanged; + KeyDown += SearchLayerKeyDown; + + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious())); + this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close())); + IsClosed = true; + } + + void textArea_DocumentChanged(object sender, EventArgs e) + { + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + currentDocument = textArea.Document; + if (currentDocument != null) { + currentDocument.TextChanged += textArea_Document_TextChanged; + DoSearch(false); + } + } + + void textArea_Document_TextChanged(object sender, EventArgs e) + { + DoSearch(false); + } + + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox; + } + + void ValidateSearchText() + { + if (searchTextBox == null) + return; + var be = searchTextBox.GetBindingExpression(TextBox.TextProperty); + try { + Validation.ClearInvalid(be); + UpdateSearch(); + } catch (SearchPatternException ex) { + var ve = new ValidationError(be.ParentBinding.ValidationRules[0], be, ex.Message, ex); + Validation.MarkInvalid(be, ve); + } + } + + /// + /// Reactivates the SearchPanel by setting the focus on the search box and selecting all text. + /// + public void Reactivate() + { + if (searchTextBox == null) + return; + searchTextBox.Focus(); + searchTextBox.SelectAll(); + } + + /// + /// Moves to the next occurrence in the file. + /// + public void FindNext() + { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + 1); + if (result == null) + result = renderer.CurrentResults.FirstSegment; + if (result != null) { + SelectResult(result); + } + } + + /// + /// Moves to the previous occurrence in the file. + /// + public void FindPrevious() + { + SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset); + if (result != null) + result = renderer.CurrentResults.GetPreviousSegment(result); + if (result == null) + result = renderer.CurrentResults.LastSegment; + if (result != null) { + SelectResult(result); + } + } + + ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = false }; + + void DoSearch(bool changeSelection) + { + if (IsClosed) + return; + renderer.CurrentResults.Clear(); + + if (!string.IsNullOrEmpty(SearchPattern)) { + int offset = textArea.Caret.Offset; + if (changeSelection) { + textArea.ClearSelection(); + } + // We cast from ISearchResult to SearchResult; this is safe because we always use the built-in strategy + foreach (SearchResult result in strategy.FindAll(textArea.Document, 0, textArea.Document.TextLength)) { + if (changeSelection && result.StartOffset >= offset) { + SelectResult(result); + changeSelection = false; + } + renderer.CurrentResults.Add(result); + } + if (!renderer.CurrentResults.Any()) { + messageView.IsOpen = true; + messageView.Content = Localization.NoMatchesFoundText; + messageView.PlacementTarget = searchTextBox; + } else + messageView.IsOpen = false; + } + textArea.TextView.InvalidateLayer(KnownLayer.Selection); + } + + void SelectResult(SearchResult result) + { + textArea.Caret.Offset = result.StartOffset; + textArea.Selection = Selection.Create(textArea, result.StartOffset, result.EndOffset); + textArea.Caret.BringCaretToView(); + // show caret even if the editor does not have the Keyboard Focus + textArea.Caret.Show(); + } + + void SearchLayerKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { + case Key.Enter: + e.Handled = true; + messageView.IsOpen = false; + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + FindPrevious(); + else + FindNext(); + if (searchTextBox != null) { + var error = Validation.GetErrors(searchTextBox).FirstOrDefault(); + if (error != null) { + messageView.Content = Localization.ErrorText + " " + error.ErrorContent; + messageView.PlacementTarget = searchTextBox; + messageView.IsOpen = true; + } + } + break; + case Key.Escape: + e.Handled = true; + Close(); + break; + } + } + + /// + /// Gets whether the Panel is already closed. + /// + public bool IsClosed { get; private set; } + + /// + /// Closes the SearchPanel. + /// + public void Close() + { + bool hasFocus = this.IsKeyboardFocusWithin; + + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Remove(adorner); + messageView.IsOpen = false; + textArea.TextView.BackgroundRenderers.Remove(renderer); + if (hasFocus) + textArea.Focus(); + IsClosed = true; + + // Clear existing search results so that the segments don't have to be maintained + renderer.CurrentResults.Clear(); + } + + /// + /// Closes the SearchPanel and removes it. + /// + public void CloseAndRemove() + { + Close(); + textArea.DocumentChanged -= textArea_DocumentChanged; + if (currentDocument != null) + currentDocument.TextChanged -= textArea_Document_TextChanged; + } + + /// + /// Opens the an existing search panel. + /// + public void Open() + { + if (!IsClosed) return; + var layer = AdornerLayer.GetAdornerLayer(textArea); + if (layer != null) + layer.Add(adorner); + textArea.TextView.BackgroundRenderers.Add(renderer); + IsClosed = false; + DoSearch(false); + } + + /// + /// Fired when SearchOptions are changed inside the SearchPanel. + /// + public event EventHandler SearchOptionsChanged; + + /// + /// Raises the event. + /// + protected virtual void OnSearchOptionsChanged(SearchOptionsChangedEventArgs e) + { + if (SearchOptionsChanged != null) { + SearchOptionsChanged(this, e); + } + } + } + + /// + /// EventArgs for event. + /// + public class SearchOptionsChangedEventArgs : EventArgs + { + /// + /// Gets the search pattern. + /// + public string SearchPattern { get; private set; } + + /// + /// Gets whether the search pattern should be interpreted case-sensitive. + /// + public bool MatchCase { get; private set; } + + /// + /// Gets whether the search pattern should be interpreted as regular expression. + /// + public bool UseRegex { get; private set; } + + /// + /// Gets whether the search pattern should only match whole words. + /// + public bool WholeWords { get; private set; } + + /// + /// Creates a new SearchOptionsChangedEventArgs instance. + /// + public SearchOptionsChangedEventArgs(string searchPattern, bool matchCase, bool useRegex, bool wholeWords) + { + this.SearchPattern = searchPattern; + this.MatchCase = matchCase; + this.UseRegex = useRegex; + this.WholeWords = wholeWords; + } + } + + class SearchPanelAdorner : Adorner + { + SearchPanel panel; + + public SearchPanelAdorner(TextArea textArea, SearchPanel panel) + : base(textArea) + { + this.panel = panel; + AddVisualChild(panel); + } + + protected override int VisualChildrenCount { + get { return 1; } + } + + protected override Visual GetVisualChild(int index) + { + if (index != 0) + throw new ArgumentOutOfRangeException(); + return panel; + } + + protected override Size ArrangeOverride(Size finalSize) + { + panel.Arrange(new Rect(new Point(0, 0), finalSize)); + return new Size(panel.ActualWidth, panel.ActualHeight); + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml new file mode 100644 index 000000000..b787e9a4f --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchPanel.xaml @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs new file mode 100644 index 000000000..35b9c0696 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchResultBackgroundRenderer.cs @@ -0,0 +1,78 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Search +{ + class SearchResultBackgroundRenderer : IBackgroundRenderer + { + TextSegmentCollection currentResults = new TextSegmentCollection(); + + public TextSegmentCollection CurrentResults { + get { return currentResults; } + } + + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public SearchResultBackgroundRenderer() + { + markerBrush = Brushes.LightGreen; + markerPen = new Pen(markerBrush, 1); + } + + Brush markerBrush; + Pen markerPen; + + public Brush MarkerBrush { + get { return markerBrush; } + set { + this.markerBrush = value; + markerPen = new Pen(markerBrush, 1); + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + + if (currentResults == 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 (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToMiddleOfPixels = true; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, result); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(markerBrush, markerPen, geometry); + } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs new file mode 100644 index 000000000..01633c62a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/SearchStrategyFactory.cs @@ -0,0 +1,68 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Controls; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Search +{ + /// + /// Provides factory methods for ISearchStrategies. + /// + public class SearchStrategyFactory + { + /// + /// Creates a default ISearchStrategy with the given parameters. + /// + public static ISearchStrategy Create(string searchPattern, bool ignoreCase, bool matchWholeWords, SearchMode mode) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; + if (ignoreCase) + options |= RegexOptions.IgnoreCase; + + switch (mode) { + case SearchMode.Normal: + searchPattern = Regex.Escape(searchPattern); + break; + case SearchMode.Wildcard: + searchPattern = ConvertWildcardsToRegex(searchPattern); + break; + } + try { + Regex pattern = new Regex(searchPattern, options); + return new RegexSearchStrategy(pattern, matchWholeWords); + } catch (ArgumentException ex) { + throw new SearchPatternException(ex.Message, ex); + } + } + + static string ConvertWildcardsToRegex(string searchPattern) + { + if (string.IsNullOrEmpty(searchPattern)) + return ""; + + StringBuilder builder = new StringBuilder(); + + foreach (char ch in searchPattern) { + switch (ch) { + case '?': + builder.Append("."); + break; + case '*': + builder.Append(".*"); + break; + default: + builder.Append(Regex.Escape(ch.ToString())); + break; + } + } + + return builder.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png new file mode 100644 index 000000000..250330790 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/next.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png new file mode 100644 index 000000000..f0454a237 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Search/prev.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs new file mode 100644 index 000000000..07275f273 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/IActiveElement.cs @@ -0,0 +1,35 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows.Media; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Represents an active element that allows the snippet to stay interactive after insertion. + /// + public interface IActiveElement + { + /// + /// Called when the all snippet elements have been inserted. + /// + void OnInsertionCompleted(); + + /// + /// Called when the interactive mode is deactivated. + /// + void Deactivate(SnippetEventArgs e); + + /// + /// Gets whether this element is editable (the user will be able to select it with Tab). + /// + bool IsEditable { get; } + + /// + /// Gets the segment associated with this element. May be null. + /// + ISegment Segment { get; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs new file mode 100644 index 000000000..d7de8311d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/InsertionContext.cs @@ -0,0 +1,269 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Represents the context of a snippet insertion. + /// + public class InsertionContext : IWeakEventListener + { + enum Status + { + Insertion, + RaisingInsertionCompleted, + Interactive, + RaisingDeactivated, + Deactivated + } + + Status currentStatus = Status.Insertion; + + /// + /// Creates a new InsertionContext instance. + /// + public InsertionContext(TextArea textArea, int insertionPosition) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.TextArea = textArea; + this.Document = textArea.Document; + this.SelectedText = textArea.Selection.GetText(); + this.InsertionPosition = insertionPosition; + this.startPosition = insertionPosition; + + DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition); + ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset); + this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset); + this.Tab = textArea.Options.IndentationString; + + this.LineTerminator = TextUtilities.GetNewLineFromDocument(this.Document, startLine.LineNumber); + } + + /// + /// Gets the text area. + /// + public TextArea TextArea { get; private set; } + + /// + /// Gets the text document. + /// + public TextDocument Document { get; private set; } + + /// + /// Gets the text that was selected before the insertion of the snippet. + /// + public string SelectedText { get; private set; } + + /// + /// Gets the indentation at the insertion position. + /// + public string Indentation { get; private set; } + + /// + /// Gets the indentation string for a single indentation level. + /// + public string Tab { get; private set; } + + /// + /// Gets the line terminator at the insertion position. + /// + public string LineTerminator { get; private set; } + + /// + /// Gets/Sets the insertion position. + /// + public int InsertionPosition { get; set; } + + readonly int startPosition; + AnchorSegment wholeSnippetAnchor; + bool deactivateIfSnippetEmpty; + + /// + /// Gets the start position of the snippet insertion. + /// + public int StartPosition { + get { + if (wholeSnippetAnchor != null) + return wholeSnippetAnchor.Offset; + else + return startPosition; + } + } + + /// + /// Inserts text at the insertion position and advances the insertion position. + /// This method will add the current indentation to every line in and will + /// replace newlines with the expected newline for the document. + /// + public void InsertText(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + if (currentStatus != Status.Insertion) + throw new InvalidOperationException(); + + text = text.Replace("\t", this.Tab); + + using (this.Document.RunUpdate()) { + int textOffset = 0; + SimpleSegment segment; + while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) { + string insertString = text.Substring(textOffset, segment.Offset - textOffset) + + this.LineTerminator + this.Indentation; + this.Document.Insert(InsertionPosition, insertString); + this.InsertionPosition += insertString.Length; + textOffset = segment.EndOffset; + } + string remainingInsertString = text.Substring(textOffset); + this.Document.Insert(InsertionPosition, remainingInsertString); + this.InsertionPosition += remainingInsertString.Length; + } + } + + Dictionary elementMap = new Dictionary(); + List registeredElements = new List(); + + /// + /// Registers an active element. Elements should be registered during insertion and will be called back + /// when insertion has completed. + /// + /// The snippet element that created the active element. + /// The active element. + public void RegisterActiveElement(SnippetElement owner, IActiveElement element) + { + if (owner == null) + throw new ArgumentNullException("owner"); + if (element == null) + throw new ArgumentNullException("element"); + if (currentStatus != Status.Insertion) + throw new InvalidOperationException(); + elementMap.Add(owner, element); + registeredElements.Add(element); + } + + /// + /// Returns the active element belonging to the specified snippet element, or null if no such active element is found. + /// + public IActiveElement GetActiveElement(SnippetElement owner) + { + if (owner == null) + throw new ArgumentNullException("owner"); + IActiveElement element; + if (elementMap.TryGetValue(owner, out element)) + return element; + else + return null; + } + + /// + /// Gets the list of active elements. + /// + public IEnumerable ActiveElements { + get { return registeredElements; } + } + + /// + /// Calls the method on all registered active elements + /// and raises the event. + /// + /// The EventArgs to use + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", + Justification="There is an event and this method is raising it.")] + public void RaiseInsertionCompleted(EventArgs e) + { + if (currentStatus != Status.Insertion) + throw new InvalidOperationException(); + if (e == null) + e = EventArgs.Empty; + + currentStatus = Status.RaisingInsertionCompleted; + int endPosition = this.InsertionPosition; + this.wholeSnippetAnchor = new AnchorSegment(Document, startPosition, endPosition - startPosition); + TextDocumentWeakEventManager.UpdateFinished.AddListener(Document, this); + deactivateIfSnippetEmpty = (endPosition != startPosition); + + foreach (IActiveElement element in registeredElements) { + element.OnInsertionCompleted(); + } + if (InsertionCompleted != null) + InsertionCompleted(this, e); + currentStatus = Status.Interactive; + if (registeredElements.Count == 0) { + // deactivate immediately if there are no interactive elements + Deactivate(new SnippetEventArgs(DeactivateReason.NoActiveElements)); + } else { + myInputHandler = new SnippetInputHandler(this); + // disable existing snippet input handlers - there can be only 1 active snippet + foreach (TextAreaStackedInputHandler h in TextArea.StackedInputHandlers) { + if (h is SnippetInputHandler) + TextArea.PopStackedInputHandler(h); + } + TextArea.PushStackedInputHandler(myInputHandler); + } + } + + SnippetInputHandler myInputHandler; + + /// + /// Occurs when the all snippet elements have been inserted. + /// + public event EventHandler InsertionCompleted; + + /// + /// Calls the method on all registered active elements. + /// + /// The EventArgs to use + public void Deactivate(SnippetEventArgs e) + { + if (currentStatus == Status.Deactivated || currentStatus == Status.RaisingDeactivated) + return; + if (currentStatus != Status.Interactive) + throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished."); + if (e == null) + e = new SnippetEventArgs(DeactivateReason.Unknown); + + TextDocumentWeakEventManager.UpdateFinished.RemoveListener(Document, this); + currentStatus = Status.RaisingDeactivated; + TextArea.PopStackedInputHandler(myInputHandler); + foreach (IActiveElement element in registeredElements) { + element.Deactivate(e); + } + if (Deactivated != null) + Deactivated(this, e); + currentStatus = Status.Deactivated; + } + + /// + /// Occurs when the interactive mode is deactivated. + /// + public event EventHandler Deactivated; + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextDocumentWeakEventManager.UpdateFinished)) { + // Deactivate if snippet is deleted. This is necessary for correctly leaving interactive + // mode if Undo is pressed after a snippet insertion. + if (wholeSnippetAnchor.Length == 0 && deactivateIfSnippetEmpty) + Deactivate(new SnippetEventArgs(DeactivateReason.Deleted)); + return true; + } + return false; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs new file mode 100644 index 000000000..0006261fb --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/Snippet.cs @@ -0,0 +1,47 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows.Documents; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// A code snippet that can be inserted into the text editor. + /// + [Serializable] + public class Snippet : SnippetContainerElement + { + /// + /// Inserts the snippet into the text area. + /// + public void Insert(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + + ISegment selection = textArea.Selection.SurroundingSegment; + int insertionPosition = textArea.Caret.Offset; + + if (selection != null) // if something is selected + // use selection start instead of caret position, + // because caret could be at end of selection or anywhere inside. + // Removal of the selected text causes the caret position to be invalid. + insertionPosition = selection.Offset + TextUtilities.GetWhitespaceAfter(textArea.Document, selection.Offset).Length; + + InsertionContext context = new InsertionContext(textArea, insertionPosition); + + using (context.Document.RunUpdate()) { + if (selection != null) + textArea.Document.Remove(insertionPosition, selection.EndOffset - insertionPosition); + Insert(context); + context.RaiseInsertionCompleted(EventArgs.Empty); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs new file mode 100644 index 000000000..cfce03b6a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetAnchorElement.cs @@ -0,0 +1,97 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Creates a named anchor that can be accessed by other SnippetElements. + /// + public sealed class SnippetAnchorElement : SnippetElement + { + /// + /// Gets or sets the name of the anchor. + /// + public string Name { get; private set; } + + /// + /// Creates a SnippetAnchorElement with the supplied name. + /// + public SnippetAnchorElement(string name) + { + this.Name = name; + } + + /// + public override void Insert(InsertionContext context) + { + TextAnchor start = context.Document.CreateAnchor(context.InsertionPosition); + start.MovementType = AnchorMovementType.BeforeInsertion; + start.SurviveDeletion = true; + AnchorSegment segment = new AnchorSegment(start, start); + context.RegisterActiveElement(this, new AnchorElement(segment, Name, context)); + } + } + + /// + /// AnchorElement created by SnippetAnchorElement. + /// + public sealed class AnchorElement : IActiveElement + { + /// + public bool IsEditable { + get { return false; } + } + + AnchorSegment segment; + InsertionContext context; + + /// + public ISegment Segment { + get { return segment; } + } + + /// + /// Creates a new AnchorElement. + /// + public AnchorElement(AnchorSegment segment, string name, InsertionContext context) + { + this.segment = segment; + this.context = context; + this.Name = name; + } + + /// + /// Gets or sets the text at the anchor. + /// + public string Text { + get { return context.Document.GetText(segment); } + set { + int offset = segment.Offset; + int length = segment.Length; + context.Document.Replace(offset, length, value); + if (length == 0) { + // replacing an empty anchor segment with text won't enlarge it, so we have to recreate it + segment = new AnchorSegment(context.Document, offset, value.Length); + } + } + } + + /// + /// Gets or sets the name of the anchor. + /// + public string Name { get; private set; } + + /// + public void OnInsertionCompleted() + { + } + + /// + public void Deactivate(SnippetEventArgs e) + { + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs new file mode 100644 index 000000000..92f6d4fd4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetBoundElement.cs @@ -0,0 +1,122 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows.Documents; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// An element that binds to a and displays the same text. + /// + [Serializable] + public class SnippetBoundElement : SnippetElement + { + SnippetReplaceableTextElement targetElement; + + /// + /// Gets/Sets the target element. + /// + public SnippetReplaceableTextElement TargetElement { + get { return targetElement; } + set { targetElement = value; } + } + + /// + /// Converts the text before copying it. + /// + public virtual string ConvertText(string input) + { + return input; + } + + /// + public override void Insert(InsertionContext context) + { + if (targetElement != null) { + TextAnchor start = context.Document.CreateAnchor(context.InsertionPosition); + start.MovementType = AnchorMovementType.BeforeInsertion; + start.SurviveDeletion = true; + string inputText = targetElement.Text; + if (inputText != null) { + context.InsertText(ConvertText(inputText)); + } + TextAnchor end = context.Document.CreateAnchor(context.InsertionPosition); + end.MovementType = AnchorMovementType.BeforeInsertion; + end.SurviveDeletion = true; + AnchorSegment segment = new AnchorSegment(start, end); + context.RegisterActiveElement(this, new BoundActiveElement(context, targetElement, this, segment)); + } + } + + /// + public override Inline ToTextRun() + { + if (targetElement != null) { + string inputText = targetElement.Text; + if (inputText != null) { + return new Italic(new Run(ConvertText(inputText))); + } + } + return base.ToTextRun(); + } + } + + sealed class BoundActiveElement : IActiveElement + { + InsertionContext context; + SnippetReplaceableTextElement targetSnippetElement; + SnippetBoundElement boundElement; + internal IReplaceableActiveElement targetElement; + AnchorSegment segment; + + public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment) + { + this.context = context; + this.targetSnippetElement = targetSnippetElement; + this.boundElement = boundElement; + this.segment = segment; + } + + public void OnInsertionCompleted() + { + targetElement = context.GetActiveElement(targetSnippetElement) as IReplaceableActiveElement; + if (targetElement != null) { + targetElement.TextChanged += targetElement_TextChanged; + } + } + + void targetElement_TextChanged(object sender, EventArgs e) + { + // Don't copy text if the segments overlap (we would get an endless loop). + // This can happen if the user deletes the text between the replaceable element and the bound element. + if (segment.GetOverlap(targetElement.Segment) == SimpleSegment.Invalid) { + int offset = segment.Offset; + int length = segment.Length; + string text = boundElement.ConvertText(targetElement.Text); + if (length != text.Length || text != context.Document.GetText(offset, length)) { + // Call replace only if we're actually changing something. + // Without this check, we would generate an empty undo group when the user pressed undo. + context.Document.Replace(offset, length, text); + if (length == 0) { + // replacing an empty anchor segment with text won't enlarge it, so we have to recreate it + segment = new AnchorSegment(context.Document, offset, text.Length); + } + } + } + } + + public void Deactivate(SnippetEventArgs e) + { + } + + public bool IsEditable { + get { return false; } + } + + public ISegment Segment { + get { return segment; } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs new file mode 100644 index 000000000..1b33abf66 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetCaretElement.cs @@ -0,0 +1,58 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Runtime.Serialization; +using System.Windows.Input; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Sets the caret position after interactive mode has finished. + /// + [Serializable] + public class SnippetCaretElement : SnippetElement + { + [OptionalField] + bool setCaretOnlyIfTextIsSelected; + + /// + /// Creates a new SnippetCaretElement. + /// + public SnippetCaretElement() + { + } + + /// + /// Creates a new SnippetCaretElement. + /// + /// + /// If set to true, the caret is set only when some text was selected. + /// This is useful when both SnippetCaretElement and SnippetSelectionElement are used in the same snippet. + /// + public SnippetCaretElement(bool setCaretOnlyIfTextIsSelected) + { + this.setCaretOnlyIfTextIsSelected = setCaretOnlyIfTextIsSelected; + } + + /// + public override void Insert(InsertionContext context) + { + if (!setCaretOnlyIfTextIsSelected || !string.IsNullOrEmpty(context.SelectedText)) + SetCaret(context); + } + + internal static void SetCaret(InsertionContext context) + { + TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition); + pos.MovementType = AnchorMovementType.BeforeInsertion; + pos.SurviveDeletion = true; + context.Deactivated += (sender, e) => { + if (e.Reason == DeactivateReason.ReturnPressed || e.Reason == DeactivateReason.NoActiveElements) { + context.TextArea.Caret.Offset = pos.Offset; + } + }; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs new file mode 100644 index 000000000..066f1a870 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetContainerElement.cs @@ -0,0 +1,49 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Documents; + +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// A snippet element that has sub-elements. + /// + [Serializable] + public class SnippetContainerElement : SnippetElement + { + NullSafeCollection elements = new NullSafeCollection(); + + /// + /// Gets the list of child elements. + /// + public IList Elements { + get { return elements; } + } + + /// + public override void Insert(InsertionContext context) + { + foreach (SnippetElement e in this.Elements) { + e.Insert(context); + } + } + + /// + public override Inline ToTextRun() + { + Span span = new Span(); + foreach (SnippetElement e in this.Elements) { + Inline r = e.ToTextRun(); + if (r != null) + span.Inlines.Add(r); + } + return span; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs new file mode 100644 index 000000000..2e2c4556e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetElement.cs @@ -0,0 +1,32 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Windows.Documents; + +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// An element inside a snippet. + /// + [Serializable] + public abstract class SnippetElement + { + /// + /// Performs insertion of the snippet. + /// + public abstract void Insert(InsertionContext context); + + /// + /// Converts the snippet to text, with replaceable fields in italic. + /// + public virtual Inline ToTextRun() + { + return null; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs new file mode 100644 index 000000000..cfc993485 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetEventArgs.cs @@ -0,0 +1,57 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Provides information about the event that occured during use of snippets. + /// + public class SnippetEventArgs : EventArgs + { + /// + /// Gets the reason for deactivation. + /// + public DeactivateReason Reason { get; private set; } + + /// + /// Creates a new SnippetEventArgs object, with a DeactivateReason. + /// + public SnippetEventArgs(DeactivateReason reason) + { + this.Reason = reason; + } + } + + /// + /// Describes the reason for deactivation of a . + /// + public enum DeactivateReason + { + /// + /// Unknown reason. + /// + Unknown, + /// + /// Snippet was deleted. + /// + Deleted, + /// + /// There are no active elements in the snippet. + /// + NoActiveElements, + /// + /// The SnippetInputHandler was detached. + /// + InputHandlerDetached, + /// + /// Return was pressed by the user. + /// + ReturnPressed, + /// + /// Escape was pressed by the user. + /// + EscapePressed + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs new file mode 100644 index 000000000..6a8de2e88 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetInputHandler.cs @@ -0,0 +1,80 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Diagnostics; +using System.Windows.Input; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; + +namespace Tango.Scripting.Editors.Snippets +{ + sealed class SnippetInputHandler : TextAreaStackedInputHandler + { + readonly InsertionContext context; + + public SnippetInputHandler(InsertionContext context) + : base(context.TextArea) + { + this.context = context; + } + + public override void Attach() + { + base.Attach(); + + SelectElement(FindNextEditableElement(-1, false)); + } + + public override void Detach() + { + base.Detach(); + context.Deactivate(new SnippetEventArgs(DeactivateReason.InputHandlerDetached)); + } + + public override void OnPreviewKeyDown(KeyEventArgs e) + { + base.OnPreviewKeyDown(e); + if (e.Key == Key.Escape) { + context.Deactivate(new SnippetEventArgs(DeactivateReason.EscapePressed)); + e.Handled = true; + } else if (e.Key == Key.Return) { + context.Deactivate(new SnippetEventArgs(DeactivateReason.ReturnPressed)); + e.Handled = true; + } else if (e.Key == Key.Tab) { + bool backwards = e.KeyboardDevice.Modifiers == ModifierKeys.Shift; + SelectElement(FindNextEditableElement(TextArea.Caret.Offset, backwards)); + e.Handled = true; + } + } + + void SelectElement(IActiveElement element) + { + if (element != null) { + TextArea.Selection = Selection.Create(TextArea, element.Segment); + TextArea.Caret.Offset = element.Segment.EndOffset; + } + } + + IActiveElement FindNextEditableElement(int offset, bool backwards) + { + IEnumerable elements = context.ActiveElements.Where(e => e.IsEditable && e.Segment != null); + if (backwards) { + elements = elements.Reverse(); + foreach (IActiveElement element in elements) { + if (offset > element.Segment.EndOffset) + return element; + } + } else { + foreach (IActiveElement element in elements) { + if (offset < element.Segment.Offset) + return element; + } + } + return elements.FirstOrDefault(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs new file mode 100644 index 000000000..9bf872f11 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs @@ -0,0 +1,213 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Text element that is supposed to be replaced by the user. + /// Will register an . + /// + [Serializable] + public class SnippetReplaceableTextElement : SnippetTextElement + { + /// + public override void Insert(InsertionContext context) + { + int start = context.InsertionPosition; + base.Insert(context); + int end = context.InsertionPosition; + context.RegisterActiveElement(this, new ReplaceableActiveElement(context, start, end)); + } + + /// + public override Inline ToTextRun() + { + return new Italic(base.ToTextRun()); + } + } + + /// + /// Interface for active element registered by . + /// + public interface IReplaceableActiveElement : IActiveElement + { + /// + /// Gets the current text inside the element. + /// + string Text { get; } + + /// + /// Occurs when the text inside the element changes. + /// + event EventHandler TextChanged; + } + + sealed class ReplaceableActiveElement : IReplaceableActiveElement, IWeakEventListener + { + readonly InsertionContext context; + readonly int startOffset, endOffset; + TextAnchor start, end; + + public ReplaceableActiveElement(InsertionContext context, int startOffset, int endOffset) + { + this.context = context; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + void AnchorDeleted(object sender, EventArgs e) + { + context.Deactivate(new SnippetEventArgs(DeactivateReason.Deleted)); + } + + public void OnInsertionCompleted() + { + // anchors must be created in OnInsertionCompleted because they should move only + // due to user insertions, not due to insertions of other snippet parts + start = context.Document.CreateAnchor(startOffset); + start.MovementType = AnchorMovementType.BeforeInsertion; + end = context.Document.CreateAnchor(endOffset); + end.MovementType = AnchorMovementType.AfterInsertion; + start.Deleted += AnchorDeleted; + end.Deleted += AnchorDeleted; + + // Be careful with references from the document to the editing/snippet layer - use weak events + // to prevent memory leaks when the text area control gets dropped from the UI while the snippet is active. + // The InsertionContext will keep us alive as long as the snippet is in interactive mode. + TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this); + + background = new Renderer { Layer = KnownLayer.Background, element = this }; + foreground = new Renderer { Layer = KnownLayer.Text, element = this }; + context.TextArea.TextView.BackgroundRenderers.Add(background); + context.TextArea.TextView.BackgroundRenderers.Add(foreground); + context.TextArea.Caret.PositionChanged += Caret_PositionChanged; + Caret_PositionChanged(null, null); + + this.Text = GetText(); + } + + public void Deactivate(SnippetEventArgs e) + { + TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this); + context.TextArea.TextView.BackgroundRenderers.Remove(background); + context.TextArea.TextView.BackgroundRenderers.Remove(foreground); + context.TextArea.Caret.PositionChanged -= Caret_PositionChanged; + } + + bool isCaretInside; + + void Caret_PositionChanged(object sender, EventArgs e) + { + ISegment s = this.Segment; + if (s != null) { + bool newIsCaretInside = s.Contains(context.TextArea.Caret.Offset); + if (newIsCaretInside != isCaretInside) { + isCaretInside = newIsCaretInside; + context.TextArea.TextView.InvalidateLayer(foreground.Layer); + } + } + } + + Renderer background, foreground; + + public string Text { get; private set; } + + string GetText() + { + if (start.IsDeleted || end.IsDeleted) + return string.Empty; + else + return context.Document.GetText(start.Offset, Math.Max(0, end.Offset - start.Offset)); + } + + public event EventHandler TextChanged; + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) { + string newText = GetText(); + if (this.Text != newText) { + this.Text = newText; + if (TextChanged != null) + TextChanged(this, e); + } + return true; + } + return false; + } + + public bool IsEditable { + get { return true; } + } + + public ISegment Segment { + get { + if (start.IsDeleted || end.IsDeleted) + return null; + else + return new SimpleSegment(start.Offset, Math.Max(0, end.Offset - start.Offset)); + } + } + + sealed class Renderer : IBackgroundRenderer + { + static readonly Brush backgroundBrush = CreateBackgroundBrush(); + static readonly Pen activeBorderPen = CreateBorderPen(); + + static Brush CreateBackgroundBrush() + { + SolidColorBrush b = new SolidColorBrush(Colors.LimeGreen); + b.Opacity = 0.4; + b.Freeze(); + return b; + } + + static Pen CreateBorderPen() + { + Pen p = new Pen(Brushes.Black, 1); + p.DashStyle = DashStyles.Dot; + p.Freeze(); + return p; + } + + internal ReplaceableActiveElement element; + + public KnownLayer Layer { get; set; } + + public void Draw(TextView textView, System.Windows.Media.DrawingContext drawingContext) + { + ISegment s = element.Segment; + if (s != null) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToMiddleOfPixels = true; + if (Layer == KnownLayer.Background) { + geoBuilder.AddSegment(textView, s); + drawingContext.DrawGeometry(backgroundBrush, null, geoBuilder.CreateGeometry()); + } else { + // draw foreground only if active + if (element.isCaretInside) { + geoBuilder.AddSegment(textView, s); + foreach (BoundActiveElement boundElement in element.context.ActiveElements.OfType()) { + if (boundElement.targetElement == element) { + geoBuilder.AddSegment(textView, boundElement.Segment); + geoBuilder.CloseFigure(); + } + } + drawingContext.DrawGeometry(null, activeBorderPen, geoBuilder.CreateGeometry()); + } + } + } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs new file mode 100644 index 000000000..4c073094b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetSelectionElement.cs @@ -0,0 +1,45 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text; +using System.Windows.Input; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Inserts the previously selected text at the selection marker. + /// + [Serializable] + public class SnippetSelectionElement : SnippetElement + { + /// + /// Gets/Sets the new indentation of the selected text. + /// + public int Indentation { get; set; } + + /// + public override void Insert(InsertionContext context) + { + StringBuilder tabString = new StringBuilder(); + + for (int i = 0; i < Indentation; i++) { + tabString.Append(context.Tab); + } + + string indent = tabString.ToString(); + + string text = context.SelectedText.TrimStart(' ', '\t'); + + text = text.Replace(context.LineTerminator, + context.LineTerminator + indent); + + context.Document.Insert(context.InsertionPosition, text); + context.InsertionPosition += text.Length; + + if (string.IsNullOrEmpty(context.SelectedText)) + SnippetCaretElement.SetCaret(context); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs new file mode 100644 index 000000000..decc18119 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetTextElement.cs @@ -0,0 +1,39 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows.Documents; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Snippets +{ + /// + /// Represents a text element in a snippet. + /// + [Serializable] + public class SnippetTextElement : SnippetElement + { + string text; + + /// + /// The text to be inserted. + /// + public string Text { + get { return text; } + set { text = value; } + } + + /// + public override void Insert(InsertionContext context) + { + if (text != null) + context.InsertText(text); + } + + /// + public override Inline ToTextRun() + { + return new Run(text ?? string.Empty); + } + } +} 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 new file mode 100644 index 000000000..ce7c361e3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors.csproj @@ -0,0 +1,634 @@ + + + + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0} + Debug + AnyCPU + Library + Tango.Scripting.Editors + Tango.Scripting.Editors + v4.6.1 + Properties + "C:\Program Files\SharpDevelop\3.0\bin\..\AddIns\AddIns\Misc\SourceAnalysis\Settings.SourceAnalysis" + False + False + 4 + false + false + ICSharpCode.AvalonEdit.snk + False + File + False + -Microsoft.Design#CA1020;-Microsoft.Design#CA1033;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810 + ..\bin\$(Configuration) + ..\bin\$(Configuration)\ICSharpCode.AvalonEdit.xml + 1607 + + + SAK + SAK + SAK + SAK + + + true + Full + False + True + DEBUG;TRACE;DOTNET4 + + + false + PdbOnly + True + False + TRACE;DOTNET4 + + + False + Auto + 4194304 + AnyCPU + 4096 + + + ..\..\Build\Scripting\Debug\ + + + false + TRACE;DEBUG + + + false + ..\..\Build\Scripting\Release\ + + + + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + ..\..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll + + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + 3.0 + + + 3.0 + + + + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + 3.5 + + + + 3.5 + + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + + + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 4.0 + + + + 3.5 + + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + + + 3.0 + + + 3.0 + + + 3.0 + + + + + GlobalVersionInfo.cs + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + UndoStack.cs + + + + + + UndoStack.cs + + + DocumentLine.cs + + + + + TextDocument.cs + + + + + TextAnchor.cs + + + TextAnchor.cs + + + + + + UndoStack.cs + + + + + ILineTracker.cs + + + + + + + + + + + + + Selection.cs + + + + + + + + + + + + + + + IReadOnlySectionProvider.cs + + + Selection.cs + + + + Selection.cs + + + Selection.cs + + + Selection.cs + + + Selection.cs + + + + + + IReadOnlySectionProvider.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IBackgroundRenderer.cs + + + HeightTree.cs + + + IVisualLineTransformer.cs + + + + + IVisualLineTransformer.cs + + + + TextView.cs + + + + HeightTree.cs + + + HeightTree.cs + + + + + VisualLineElementGenerator.cs + + + TextView.cs + + + + TextView.cs + + + TextView.cs + + + + + FormattedTextElement.cs + + + + TextView.cs + + + + + TextView.cs + + + + + + + VisualLine.cs + + + + + + VisualLine.cs + + + VisualLineElementGenerator.cs + + + VisualLine.cs + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + TextDocument.cs + + + TextDocument.cs + + + + DocumentLine.cs + + + + + TextEditor.cs + + + + + ObserveAddRemoveCollection.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AXmlParser.cs + + + + + + + + + AXmlParser.cs + + + AXmlParser.cs + + + AXmlText.cs + + + AXmlParser.cs + + + + + + + + + + + + + + + + + + + + + + + + SearchPanel.cs + + + DropDownButton.cs + + + + MSBuild:Compile + Designer + + + Designer + + + + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {1e938fd2-c669-4738-98c9-77f96ce4d451} + Tango.Scripting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs new file mode 100644 index 000000000..d2fc9e02b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs @@ -0,0 +1,1144 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Highlighting; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors +{ + /// + /// The text editor control. + /// Contains a scrollable TextArea. + /// + [Localizability(LocalizationCategory.Text), ContentProperty("Text")] + public class TextEditor : Control, ITextEditorComponent, IServiceProvider, IWeakEventListener + { + #region Constructors + static TextEditor() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor), + new FrameworkPropertyMetadata(typeof(TextEditor))); + FocusableProperty.OverrideMetadata(typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.True)); + } + + /// + /// Creates a new TextEditor instance. + /// + public TextEditor() : this(new TextArea()) + { + } + + /// + /// Creates a new TextEditor instance. + /// + protected TextEditor(TextArea textArea) + { + if (textArea == null) + throw new ArgumentNullException("textArea"); + this.textArea = textArea; + + textArea.TextView.Services.AddService(typeof(TextEditor), this); + + SetCurrentValue(OptionsProperty, textArea.Options); + SetCurrentValue(DocumentProperty, new TextDocument()); + } + + #endregion + + /// + protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() + { + return new TextEditorAutomationPeer(this); + } + + /// Forward focus to TextArea. + /// + protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnGotKeyboardFocus(e); + if (e.NewFocus == this) { + Keyboard.Focus(this.TextArea); + e.Handled = true; + } + } + + #region Document property + /// + /// Document property. + /// + public static readonly DependencyProperty DocumentProperty + = TextView.DocumentProperty.AddOwner( + typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged)); + + /// + /// Gets/Sets the document displayed by the text editor. + /// This is a dependency property. + /// + public TextDocument Document { + get { return (TextDocument)GetValue(DocumentProperty); } + set { SetValue(DocumentProperty, value); } + } + + /// + /// Occurs when the document property has changed. + /// + public event EventHandler DocumentChanged; + + /// + /// Raises the event. + /// + protected virtual void OnDocumentChanged(EventArgs e) + { + if (DocumentChanged != null) { + DocumentChanged(this, e); + } + } + + static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextEditor)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue); + } + + void OnDocumentChanged(TextDocument oldValue, TextDocument newValue) + { + if (oldValue != null) { + TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this); + PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile"); + } + textArea.Document = newValue; + if (newValue != null) { + TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this); + PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile"); + } + OnDocumentChanged(EventArgs.Empty); + OnTextChanged(EventArgs.Empty); + } + #endregion + + #region Options property + /// + /// Options property. + /// + public static readonly DependencyProperty OptionsProperty + = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged)); + + /// + /// Gets/Sets the options currently used by the text editor. + /// + public TextEditorOptions Options { + get { return (TextEditorOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } + + /// + /// Occurs when a text editor option has changed. + /// + public event PropertyChangedEventHandler OptionChanged; + + /// + /// Raises the event. + /// + protected virtual void OnOptionChanged(PropertyChangedEventArgs e) + { + if (OptionChanged != null) { + OptionChanged(this, e); + } + } + + static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + ((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue); + } + + void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue) + { + if (oldValue != null) { + PropertyChangedWeakEventManager.RemoveListener(oldValue, this); + } + textArea.Options = newValue; + if (newValue != null) { + PropertyChangedWeakEventManager.AddListener(newValue, this); + } + OnOptionChanged(new PropertyChangedEventArgs(null)); + } + + /// + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(PropertyChangedWeakEventManager)) { + OnOptionChanged((PropertyChangedEventArgs)e); + return true; + } else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) { + OnTextChanged(e); + return true; + } else if (managerType == typeof(PropertyChangedEventManager)) { + return HandleIsOriginalChanged((PropertyChangedEventArgs)e); + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + #endregion + + #region Text property + /// + /// Gets/Sets the text of the current document. + /// + [Localizability(LocalizationCategory.Text), DefaultValue("")] + public string Text { + get { + TextDocument document = this.Document; + return document != null ? document.Text : string.Empty; + } + set { + TextDocument document = GetDocument(); + document.Text = value ?? string.Empty; + // after replacing the full text, the caret is positioned at the end of the document + // - reset it to the beginning. + this.CaretOffset = 0; + document.UndoStack.ClearAll(); + } + } + + TextDocument GetDocument() + { + TextDocument document = this.Document; + if (document == null) + throw ThrowUtil.NoDocumentAssigned(); + return document; + } + + /// + /// Occurs when the Text property changes. + /// + public event EventHandler TextChanged; + + /// + /// Raises the event. + /// + protected virtual void OnTextChanged(EventArgs e) + { + if (TextChanged != null) { + TextChanged(this, e); + } + } + #endregion + + #region TextArea / ScrollViewer properties + readonly TextArea textArea; + ScrollViewer scrollViewer; + + /// + /// Is called after the template was applied. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this); + } + + /// + /// Gets the text area. + /// + public TextArea TextArea { + get { + return textArea; + } + } + + /// + /// Gets the scroll viewer used by the text editor. + /// This property can return null if the template has not been applied / does not contain a scroll viewer. + /// + internal ScrollViewer ScrollViewer { + get { return scrollViewer; } + } + + bool CanExecute(RoutedUICommand command) + { + TextArea textArea = this.TextArea; + if (textArea == null) + return false; + else + return command.CanExecute(null, textArea); + } + + void Execute(RoutedUICommand command) + { + TextArea textArea = this.TextArea; + if (textArea != null) + command.Execute(null, textArea); + } + #endregion + + #region Syntax highlighting + /// + /// The property. + /// + public static readonly DependencyProperty SyntaxHighlightingProperty = + DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor), + new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged)); + + + /// + /// Gets/sets the syntax highlighting definition used to colorize the text. + /// + public IHighlightingDefinition SyntaxHighlighting { + get { return (IHighlightingDefinition)GetValue(SyntaxHighlightingProperty); } + set { SetValue(SyntaxHighlightingProperty, value); } + } + + IVisualLineTransformer colorizer; + + static void OnSyntaxHighlightingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TextEditor)d).OnSyntaxHighlightingChanged(e.NewValue as IHighlightingDefinition); + } + + void OnSyntaxHighlightingChanged(IHighlightingDefinition newValue) + { + if (colorizer != null) { + this.TextArea.TextView.LineTransformers.Remove(colorizer); + colorizer = null; + } + if (newValue != null) { + colorizer = CreateColorizer(newValue); + this.TextArea.TextView.LineTransformers.Insert(0, colorizer); + } + } + + /// + /// Creates the highlighting colorizer for the specified highlighting definition. + /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions. + /// + /// + protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) + { + if (highlightingDefinition == null) + throw new ArgumentNullException("highlightingDefinition"); + return new HighlightingColorizer(highlightingDefinition.MainRuleSet); + } + #endregion + + #region WordWrap + /// + /// Word wrap dependency property. + /// + public static readonly DependencyProperty WordWrapProperty = + DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False)); + + /// + /// Specifies whether the text editor uses word wrapping. + /// + /// + /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the + /// HorizontalScrollBarVisibility setting. + /// + public bool WordWrap { + get { return (bool)GetValue(WordWrapProperty); } + set { SetValue(WordWrapProperty, Boxes.Box(value)); } + } + #endregion + + #region IsReadOnly + /// + /// IsReadOnly dependency property. + /// + public static readonly DependencyProperty IsReadOnlyProperty = + DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged)); + + /// + /// Specifies whether the user can change the text editor content. + /// Setting this property will replace the + /// TextArea.ReadOnlySectionProvider. + /// + public bool IsReadOnly { + get { return (bool)GetValue(IsReadOnlyProperty); } + set { SetValue(IsReadOnlyProperty, Boxes.Box(value)); } + } + + static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = d as TextEditor; + if (editor != null) { + if ((bool)e.NewValue) + editor.TextArea.ReadOnlySectionProvider = ReadOnlyDocument.Instance; + else + editor.TextArea.ReadOnlySectionProvider = NoReadOnlySections.Instance; + + TextEditorAutomationPeer peer = TextEditorAutomationPeer.FromElement(editor) as TextEditorAutomationPeer; + if (peer != null) { + peer.RaiseIsReadOnlyChanged((bool)e.OldValue, (bool)e.NewValue); + } + } + } + #endregion + + #region IsModified + /// + /// Dependency property for + /// + public static readonly DependencyProperty IsModifiedProperty = + DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged)); + + /// + /// Gets/Sets the 'modified' flag. + /// + public bool IsModified { + get { return (bool)GetValue(IsModifiedProperty); } + set { SetValue(IsModifiedProperty, Boxes.Box(value)); } + } + + static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = d as TextEditor; + if (editor != null) { + TextDocument document = editor.Document; + if (document != null) { + UndoStack undoStack = document.UndoStack; + if ((bool)e.NewValue) { + if (undoStack.IsOriginalFile) + undoStack.DiscardOriginalFileMarker(); + } else { + undoStack.MarkAsOriginalFile(); + } + } + } + } + + bool HandleIsOriginalChanged(PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsOriginalFile") { + TextDocument document = this.Document; + if (document != null) { + SetCurrentValue(IsModifiedProperty, Boxes.Box(!document.UndoStack.IsOriginalFile)); + } + return true; + } else { + return false; + } + } + #endregion + + #region ShowLineNumbers + /// + /// ShowLineNumbers dependency property. + /// + public static readonly DependencyProperty ShowLineNumbersProperty = + DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged)); + + /// + /// Specifies whether line numbers are shown on the left to the text view. + /// + public bool ShowLineNumbers { + get { return (bool)GetValue(ShowLineNumbersProperty); } + set { SetValue(ShowLineNumbersProperty, Boxes.Box(value)); } + } + + static void OnShowLineNumbersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = (TextEditor)d; + var leftMargins = editor.TextArea.LeftMargins; + if ((bool)e.NewValue) { + LineNumberMargin lineNumbers = new LineNumberMargin(); + Line line = (Line)DottedLineMargin.Create(); + leftMargins.Insert(0, lineNumbers); + leftMargins.Insert(1, line); + var lineNumbersForeground = new Binding("LineNumbersForeground") { Source = editor }; + line.SetBinding(Line.StrokeProperty, lineNumbersForeground); + lineNumbers.SetBinding(Control.ForegroundProperty, lineNumbersForeground); + } else { + for (int i = 0; i < leftMargins.Count; i++) { + if (leftMargins[i] is LineNumberMargin) { + leftMargins.RemoveAt(i); + if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i])) { + leftMargins.RemoveAt(i); + } + break; + } + } + } + } + #endregion + + #region LineNumbersForeground + /// + /// LineNumbersForeground dependency property. + /// + public static readonly DependencyProperty LineNumbersForegroundProperty = + DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor), + new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged)); + + /// + /// Gets/sets the Brush used for displaying the foreground color of line numbers. + /// + public Brush LineNumbersForeground { + get { return (Brush)GetValue(LineNumbersForegroundProperty); } + set { SetValue(LineNumbersForegroundProperty, value); } + } + + static void OnLineNumbersForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TextEditor editor = (TextEditor)d; + var lineNumberMargin = editor.TextArea.LeftMargins.FirstOrDefault(margin => margin is LineNumberMargin) as LineNumberMargin;; + + if (lineNumberMargin != null) { + lineNumberMargin.SetValue(Control.ForegroundProperty, e.NewValue); + } + } + #endregion + + #region TextBoxBase-like methods + /// + /// Appends text to the end of the document. + /// + public void AppendText(string textData) + { + var document = GetDocument(); + document.Insert(document.TextLength, textData); + } + + /// + /// Begins a group of document changes. + /// + public void BeginChange() + { + GetDocument().BeginUpdate(); + } + + /// + /// Copies the current selection to the clipboard. + /// + public void Copy() + { + Execute(ApplicationCommands.Copy); + } + + /// + /// Removes the current selection and copies it to the clipboard. + /// + public void Cut() + { + Execute(ApplicationCommands.Cut); + } + + /// + /// Begins a group of document changes and returns an object that ends the group of document + /// changes when it is disposed. + /// + public IDisposable DeclareChangeBlock() + { + return GetDocument().RunUpdate(); + } + + /// + /// Ends the current group of document changes. + /// + public void EndChange() + { + GetDocument().EndUpdate(); + } + + /// + /// Scrolls one line down. + /// + public void LineDown() + { + if (scrollViewer != null) + scrollViewer.LineDown(); + } + + /// + /// Scrolls to the left. + /// + public void LineLeft() + { + if (scrollViewer != null) + scrollViewer.LineLeft(); + } + + /// + /// Scrolls to the right. + /// + public void LineRight() + { + if (scrollViewer != null) + scrollViewer.LineRight(); + } + + /// + /// Scrolls one line up. + /// + public void LineUp() + { + if (scrollViewer != null) + scrollViewer.LineUp(); + } + + /// + /// Scrolls one page down. + /// + public void PageDown() + { + if (scrollViewer != null) + scrollViewer.PageDown(); + } + + /// + /// Scrolls one page up. + /// + public void PageUp() + { + if (scrollViewer != null) + scrollViewer.PageUp(); + } + + /// + /// Scrolls one page left. + /// + public void PageLeft() + { + if (scrollViewer != null) + scrollViewer.PageLeft(); + } + + /// + /// Scrolls one page right. + /// + public void PageRight() + { + if (scrollViewer != null) + scrollViewer.PageRight(); + } + + /// + /// Pastes the clipboard content. + /// + public void Paste() + { + Execute(ApplicationCommands.Paste); + } + + /// + /// Redoes the most recent undone command. + /// + /// True is the redo operation was successful, false is the redo stack is empty. + public bool Redo() + { + if (CanExecute(ApplicationCommands.Redo)) { + Execute(ApplicationCommands.Redo); + return true; + } + return false; + } + + /// + /// Scrolls to the end of the document. + /// + public void ScrollToEnd() + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToEnd(); + } + + /// + /// Scrolls to the start of the document. + /// + public void ScrollToHome() + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToHome(); + } + + /// + /// Scrolls to the specified position in the document. + /// + public void ScrollToHorizontalOffset(double offset) + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToHorizontalOffset(offset); + } + + /// + /// Scrolls to the specified position in the document. + /// + public void ScrollToVerticalOffset(double offset) + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToVerticalOffset(offset); + } + + /// + /// Selects the entire text. + /// + public void SelectAll() + { + Execute(ApplicationCommands.SelectAll); + } + + /// + /// Undoes the most recent command. + /// + /// True is the undo operation was successful, false is the undo stack is empty. + public bool Undo() + { + if (CanExecute(ApplicationCommands.Undo)) { + Execute(ApplicationCommands.Undo); + return true; + } + return false; + } + + /// + /// Gets if the most recent undone command can be redone. + /// + public bool CanRedo { + get { return CanExecute(ApplicationCommands.Redo); } + } + + /// + /// Gets if the most recent command can be undone. + /// + public bool CanUndo { + get { return CanExecute(ApplicationCommands.Undo); } + } + + /// + /// Gets the vertical size of the document. + /// + public double ExtentHeight { + get { + return scrollViewer != null ? scrollViewer.ExtentHeight : 0; + } + } + + /// + /// Gets the horizontal size of the current document region. + /// + public double ExtentWidth { + get { + return scrollViewer != null ? scrollViewer.ExtentWidth : 0; + } + } + + /// + /// Gets the horizontal size of the viewport. + /// + public double ViewportHeight { + get { + return scrollViewer != null ? scrollViewer.ViewportHeight : 0; + } + } + + /// + /// Gets the horizontal size of the viewport. + /// + public double ViewportWidth { + get { + return scrollViewer != null ? scrollViewer.ViewportWidth : 0; + } + } + + /// + /// Gets the vertical scroll position. + /// + public double VerticalOffset { + get { + return scrollViewer != null ? scrollViewer.VerticalOffset : 0; + } + } + + /// + /// Gets the horizontal scroll position. + /// + public double HorizontalOffset { + get { + return scrollViewer != null ? scrollViewer.HorizontalOffset : 0; + } + } + #endregion + + #region TextBox methods + /// + /// Gets/Sets the selected text. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string SelectedText { + get { + TextArea textArea = this.TextArea; + // We'll get the text from the whole surrounding segment. + // This is done to ensure that SelectedText.Length == SelectionLength. + if (textArea != null && textArea.Document != null && !textArea.Selection.IsEmpty) + return textArea.Document.GetText(textArea.Selection.SurroundingSegment); + else + return string.Empty; + } + set { + if (value == null) + throw new ArgumentNullException("value"); + TextArea textArea = this.TextArea; + if (textArea != null && textArea.Document != null) { + int offset = this.SelectionStart; + int length = this.SelectionLength; + textArea.Document.Replace(offset, length, value); + // keep inserted text selected + textArea.Selection = SimpleSelection.Create(textArea, offset, offset + value.Length); + } + } + } + + /// + /// Gets/sets the caret position. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int CaretOffset { + get { + TextArea textArea = this.TextArea; + if (textArea != null) + return textArea.Caret.Offset; + else + return 0; + } + set { + TextArea textArea = this.TextArea; + if (textArea != null) + textArea.Caret.Offset = value; + } + } + + /// + /// Gets/sets the start position of the selection. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int SelectionStart { + get { + TextArea textArea = this.TextArea; + if (textArea != null) { + if (textArea.Selection.IsEmpty) + return textArea.Caret.Offset; + else + return textArea.Selection.SurroundingSegment.Offset; + } else { + return 0; + } + } + set { + Select(value, SelectionLength); + } + } + + /// + /// Gets/sets the length of the selection. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int SelectionLength { + get { + TextArea textArea = this.TextArea; + if (textArea != null && !textArea.Selection.IsEmpty) + return textArea.Selection.SurroundingSegment.Length; + else + return 0; + } + set { + Select(SelectionStart, value); + } + } + + /// + /// Selects the specified text section. + /// + public void Select(int start, int length) + { + int documentLength = Document != null ? Document.TextLength : 0; + if (start < 0 || start > documentLength) + throw new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength); + if (length < 0 || start + length > documentLength) + throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - length)); + textArea.Selection = SimpleSelection.Create(textArea, start, start + length); + textArea.Caret.Offset = start + length; + } + + /// + /// Gets the number of lines in the document. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int LineCount { + get { + TextDocument document = this.Document; + if (document != null) + return document.LineCount; + else + return 1; + } + } + + /// + /// Clears the text. + /// + public void Clear() + { + this.Text = string.Empty; + } + #endregion + + #region Loading from stream + /// + /// Loads the text from the stream, auto-detecting the encoding. + /// + /// + /// This method sets to false. + /// + public void Load(Stream stream) + { + using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) { + this.Text = reader.ReadToEnd(); + this.Encoding = reader.CurrentEncoding; // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding + } + this.IsModified = false; + } + + /// + /// Loads the text from the stream, auto-detecting the encoding. + /// + public void Load(string fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { + Load(fs); + } + } + + /// + /// Gets/sets the encoding used when the file is saved. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Encoding Encoding { get; set; } + + /// + /// Saves the text to the stream. + /// + /// + /// This method sets to false. + /// + public void Save(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + StreamWriter writer = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8); + writer.Write(this.Text); + writer.Flush(); + // do not close the stream + this.IsModified = false; + } + + /// + /// Saves the text to the file. + /// + public void Save(string fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) { + Save(fs); + } + } + #endregion + + #region MouseHover events + /// + /// The PreviewMouseHover event. + /// + public static readonly RoutedEvent PreviewMouseHoverEvent = + TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); + + /// + /// The MouseHover event. + /// + public static readonly RoutedEvent MouseHoverEvent = + TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); + + + /// + /// The PreviewMouseHoverStopped event. + /// + public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = + TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + /// + /// The MouseHoverStopped event. + /// + public static readonly RoutedEvent MouseHoverStoppedEvent = + TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + + /// + /// Occurs when the mouse has hovered over a fixed location for some time. + /// + public event MouseEventHandler PreviewMouseHover { + add { AddHandler(PreviewMouseHoverEvent, value); } + remove { RemoveHandler(PreviewMouseHoverEvent, value); } + } + + /// + /// Occurs when the mouse has hovered over a fixed location for some time. + /// + public event MouseEventHandler MouseHover { + add { AddHandler(MouseHoverEvent, value); } + remove { RemoveHandler(MouseHoverEvent, value); } + } + + /// + /// Occurs when the mouse had previously hovered but now started moving again. + /// + public event MouseEventHandler PreviewMouseHoverStopped { + add { AddHandler(PreviewMouseHoverStoppedEvent, value); } + remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } + } + + /// + /// Occurs when the mouse had previously hovered but now started moving again. + /// + public event MouseEventHandler MouseHoverStopped { + add { AddHandler(MouseHoverStoppedEvent, value); } + remove { RemoveHandler(MouseHoverStoppedEvent, value); } + } + #endregion + + #region ScrollBarVisibility + /// + /// Dependency property for + /// + public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); + + /// + /// Gets/Sets the horizontal scroll bar visibility. + /// + public ScrollBarVisibility HorizontalScrollBarVisibility { + get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); } + set { SetValue(HorizontalScrollBarVisibilityProperty, value); } + } + + /// + /// Dependency property for + /// + public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); + + /// + /// Gets/Sets the vertical scroll bar visibility. + /// + public ScrollBarVisibility VerticalScrollBarVisibility { + get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); } + set { SetValue(VerticalScrollBarVisibilityProperty, value); } + } + #endregion + + object IServiceProvider.GetService(Type serviceType) + { + return textArea.GetService(serviceType); + } + + /// + /// Gets the text view position from a point inside the editor. + /// + /// The position, relative to top left + /// corner of TextEditor control + /// The text view position, or null if the point is outside the document. + public TextViewPosition? GetPositionFromPoint(Point point) + { + if (this.Document == null) + return null; + TextView textView = this.TextArea.TextView; + return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset); + } + + /// + /// Scrolls to the specified line. + /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). + /// + public void ScrollToLine(int line) + { + ScrollTo(line, -1); + } + + /// + /// Scrolls to the specified line/column. + /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). + /// + public void ScrollTo(int line, int column) + { + const double MinimumScrollPercentage = 0.3; + + TextView textView = textArea.TextView; + TextDocument document = textView.Document; + if (scrollViewer != null && document != null) { + if (line < 1) + line = 1; + if (line > document.LineCount) + line = document.LineCount; + + IScrollInfo scrollInfo = textView; + if (!scrollInfo.CanHorizontallyScroll) { + // Word wrap is enabled. Ensure that we have up-to-date info about line height so that we scroll + // to the correct position. + // This avoids that the user has to repeat the ScrollTo() call several times when there are very long lines. + VisualLine vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line)); + double remainingHeight = scrollViewer.ViewportHeight / 2; + while (remainingHeight > 0) { + DocumentLine prevLine = vl.FirstDocumentLine.PreviousLine; + if (prevLine == null) + break; + vl = textView.GetOrConstructVisualLine(prevLine); + remainingHeight -= vl.Height; + } + } + + Point p = textArea.TextView.GetVisualPosition(new TextViewPosition(line, Math.Max(1, column)), VisualYPosition.LineMiddle); + double verticalPos = p.Y - scrollViewer.ViewportHeight / 2; + if (Math.Abs(verticalPos - scrollViewer.VerticalOffset) > MinimumScrollPercentage * scrollViewer.ViewportHeight) { + scrollViewer.ScrollToVerticalOffset(Math.Max(0, verticalPos)); + } + if (column > 0) { + if (p.X > scrollViewer.ViewportWidth - Caret.MinimumDistanceToViewBorder * 2) { + double horizontalPos = Math.Max(0, p.X - scrollViewer.ViewportWidth / 2); + if (Math.Abs(horizontalPos - scrollViewer.HorizontalOffset) > MinimumScrollPercentage * scrollViewer.ViewportWidth) { + scrollViewer.ScrollToHorizontalOffset(horizontalPos); + } + } else { + scrollViewer.ScrollToHorizontalOffset(0); + } + } + } + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.xaml new file mode 100644 index 000000000..c7d29b4e1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.xaml @@ -0,0 +1,79 @@ + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorAutomationPeer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorAutomationPeer.cs new file mode 100644 index 000000000..b32ebf224 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorAutomationPeer.cs @@ -0,0 +1,65 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Automation.Provider; +using System.Windows.Controls; + +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors +{ + /// + /// Exposes to automation. + /// + public class TextEditorAutomationPeer : FrameworkElementAutomationPeer, IValueProvider + { + /// + /// Creates a new TextEditorAutomationPeer instance. + /// + public TextEditorAutomationPeer(TextEditor owner) : base(owner) + { + Debug.WriteLine("TextEditorAutomationPeer was created"); + } + + private TextEditor TextEditor { + get { return (TextEditor)base.Owner; } + } + + void IValueProvider.SetValue(string value) + { + this.TextEditor.Text = value; + } + + string IValueProvider.Value { + get { return this.TextEditor.Text; } + } + + bool IValueProvider.IsReadOnly { + get { return this.TextEditor.IsReadOnly; } + } + + /// + public override object GetPattern(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.Value) + return this; + + if (patternInterface == PatternInterface.Scroll) { + ScrollViewer scrollViewer = this.TextEditor.ScrollViewer; + if (scrollViewer != null) + return UIElementAutomationPeer.CreatePeerForElement(scrollViewer); + } + + return base.GetPattern(patternInterface); + } + + internal void RaiseIsReadOnlyChanged(bool oldValue, bool newValue) + { + RaisePropertyChangedEvent(ValuePatternIdentifiers.IsReadOnlyProperty, Boxes.Box(oldValue), Boxes.Box(newValue)); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorComponent.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorComponent.cs new file mode 100644 index 000000000..0d333c1f2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorComponent.cs @@ -0,0 +1,40 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Editing; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors +{ + /// + /// Represents a text editor control (, + /// or ). + /// + public interface ITextEditorComponent : IServiceProvider + { + /// + /// Gets the document being edited. + /// + TextDocument Document { get; } + + /// + /// Occurs when the Document property changes (when the text editor is connected to another + /// document - not when the document content changes). + /// + event EventHandler DocumentChanged; + + /// + /// Gets the options of the text editor. + /// + TextEditorOptions Options { get; } + + /// + /// Occurs when the Options property changes, or when an option inside the current option list + /// changes. + /// + event PropertyChangedEventHandler OptionChanged; + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorOptions.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorOptions.cs new file mode 100644 index 000000000..3dcc99eab --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorOptions.cs @@ -0,0 +1,434 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Reflection; +using System.Text; + +namespace Tango.Scripting.Editors +{ + /// + /// A container for the text editor options. + /// + [Serializable] + public class TextEditorOptions : INotifyPropertyChanged + { + #region ctor + /// + /// Initializes an empty instance of TextEditorOptions. + /// + public TextEditorOptions() + { + } + + /// + /// Initializes a new instance of TextEditorOptions by copying all values + /// from to the new instance. + /// + public TextEditorOptions(TextEditorOptions options) + { + // get all the fields in the class + FieldInfo[] fields = typeof(TextEditorOptions).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + + // copy each value over to 'this' + foreach(FieldInfo fi in fields) { + if (!fi.IsNotSerialized) + fi.SetValue(this, fi.GetValue(options)); + } + } + #endregion + + #region PropertyChanged handling + /// + [field: NonSerialized] + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Raises the PropertyChanged event. + /// + /// The name of the changed property. + protected void OnPropertyChanged(string propertyName) + { + OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Raises the PropertyChanged event. + /// + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) { + PropertyChanged(this, e); + } + } + #endregion + + #region ShowSpaces / ShowTabs / ShowEndOfLine / ShowBoxForControlCharacters + bool showSpaces; + + /// + /// Gets/Sets whether to show · for spaces. + /// + /// The default value is false. + [DefaultValue(false)] + public virtual bool ShowSpaces { + get { return showSpaces; } + set { + if (showSpaces != value) { + showSpaces = value; + OnPropertyChanged("ShowSpaces"); + } + } + } + + bool showTabs; + + /// + /// Gets/Sets whether to show » for tabs. + /// + /// The default value is false. + [DefaultValue(false)] + public virtual bool ShowTabs { + get { return showTabs; } + set { + if (showTabs != value) { + showTabs = value; + OnPropertyChanged("ShowTabs"); + } + } + } + + bool showEndOfLine; + + /// + /// Gets/Sets whether to show ¶ at the end of lines. + /// + /// The default value is false. + [DefaultValue(false)] + public virtual bool ShowEndOfLine { + get { return showEndOfLine; } + set { + if (showEndOfLine != value) { + showEndOfLine = value; + OnPropertyChanged("ShowEndOfLine"); + } + } + } + + bool showBoxForControlCharacters = true; + + /// + /// Gets/Sets whether to show a box with the hex code for control characters. + /// + /// The default value is true. + [DefaultValue(true)] + public virtual bool ShowBoxForControlCharacters { + get { return showBoxForControlCharacters; } + set { + if (showBoxForControlCharacters != value) { + showBoxForControlCharacters = value; + OnPropertyChanged("ShowBoxForControlCharacters"); + } + } + } + #endregion + + #region EnableHyperlinks + bool enableHyperlinks = true; + + /// + /// Gets/Sets whether to enable clickable hyperlinks in the editor. + /// + /// The default value is true. + [DefaultValue(true)] + public virtual bool EnableHyperlinks { + get { return enableHyperlinks; } + set { + if (enableHyperlinks != value) { + enableHyperlinks = value; + OnPropertyChanged("EnableHyperlinks"); + } + } + } + + bool enableEmailHyperlinks = true; + + /// + /// Gets/Sets whether to enable clickable hyperlinks for e-mail addresses in the editor. + /// + /// The default value is true. + [DefaultValue(true)] + public virtual bool EnableEmailHyperlinks { + get { return enableEmailHyperlinks; } + set { + if (enableEmailHyperlinks != value) { + enableEmailHyperlinks = value; + OnPropertyChanged("EnableEMailHyperlinks"); + } + } + } + + bool requireControlModifierForHyperlinkClick = true; + + /// + /// Gets/Sets whether the user needs to press Control to click hyperlinks. + /// The default value is true. + /// + /// The default value is true. + [DefaultValue(true)] + public virtual bool RequireControlModifierForHyperlinkClick { + get { return requireControlModifierForHyperlinkClick; } + set { + if (requireControlModifierForHyperlinkClick != value) { + requireControlModifierForHyperlinkClick = value; + OnPropertyChanged("RequireControlModifierForHyperlinkClick"); + } + } + } + #endregion + + #region TabSize / IndentationSize / ConvertTabsToSpaces / GetIndentationString + // I'm using '_' prefixes for the fields here to avoid confusion with the local variables + // in the methods below. + // The fields should be accessed only by their property - the fields might not be used + // if someone overrides the property. + + int _indentationSize = 4; + + /// + /// Gets/Sets the width of one indentation unit. + /// + /// The default value is 4. + [DefaultValue(4)] + public virtual int IndentationSize { + get { return _indentationSize; } + set { + if (value < 1) + throw new ArgumentOutOfRangeException("value", value, "value must be positive"); + // sanity check; a too large value might cause WPF to crash internally much later + // (it only crashed in the hundred thousands for me; but might crash earlier with larger fonts) + if (value > 1000) + throw new ArgumentOutOfRangeException("value", value, "indentation size is too large"); + if (_indentationSize != value) { + _indentationSize = value; + OnPropertyChanged("IndentationSize"); + OnPropertyChanged("IndentationString"); + } + } + } + + bool _convertTabsToSpaces; + + /// + /// Gets/Sets whether to use spaces for indentation instead of tabs. + /// + /// The default value is false. + [DefaultValue(false)] + public virtual bool ConvertTabsToSpaces { + get { return _convertTabsToSpaces; } + set { + if (_convertTabsToSpaces != value) { + _convertTabsToSpaces = value; + OnPropertyChanged("ConvertTabsToSpaces"); + OnPropertyChanged("IndentationString"); + } + } + } + + /// + /// Gets the text used for indentation. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + [Browsable(false)] + public string IndentationString { + get { return GetIndentationString(1); } + } + + /// + /// Gets text required to indent from the specified to the next indentation level. + /// + public virtual string GetIndentationString(int column) + { + if (column < 1) + throw new ArgumentOutOfRangeException("column", column, "Value must be at least 1."); + int indentationSize = this.IndentationSize; + if (ConvertTabsToSpaces) { + return new string(' ', indentationSize - ((column - 1) % indentationSize)); + } else { + return "\t"; + } + } + #endregion + + bool cutCopyWholeLine = true; + + /// + /// Gets/Sets whether copying without a selection copies the whole current line. + /// + [DefaultValue(true)] + public virtual bool CutCopyWholeLine { + get { return cutCopyWholeLine; } + set { + if (cutCopyWholeLine != value) { + cutCopyWholeLine = value; + OnPropertyChanged("CutCopyWholeLine"); + } + } + } + + bool allowScrollBelowDocument; + + /// + /// Gets/Sets whether the user can scroll below the bottom of the document. + /// The default value is false; but it a good idea to set this property to true when using folding. + /// + [DefaultValue(false)] + public virtual bool AllowScrollBelowDocument { + get { return allowScrollBelowDocument; } + set { + if (allowScrollBelowDocument != value) { + allowScrollBelowDocument = value; + OnPropertyChanged("AllowScrollBelowDocument"); + } + } + } + + double wordWrapIndentation = 0; + + /// + /// Gets/Sets the indentation used for all lines except the first when word-wrapping. + /// The default value is 0. + /// + [DefaultValue(0.0)] + public virtual double WordWrapIndentation { + get { return wordWrapIndentation; } + set { + if (double.IsNaN(value) || double.IsInfinity(value)) + throw new ArgumentOutOfRangeException("value", value, "value must not be NaN/infinity"); + if (value != wordWrapIndentation) { + wordWrapIndentation = value; + OnPropertyChanged("WordWrapIndentation"); + } + } + } + + bool inheritWordWrapIndentation = true; + + /// + /// Gets/Sets whether the indentation is inherited from the first line when word-wrapping. + /// The default value is true. + /// + /// When combined with , the inherited indentation is added to the word wrap indentation. + [DefaultValue(true)] + public virtual bool InheritWordWrapIndentation { + get { return inheritWordWrapIndentation; } + set { + if (value != inheritWordWrapIndentation) { + inheritWordWrapIndentation = value; + OnPropertyChanged("InheritWordWrapIndentation"); + } + } + } + + bool enableRectangularSelection = true; + + /// + /// Enables rectangular selection (press ALT and select a rectangle) + /// + [DefaultValue(true)] + public bool EnableRectangularSelection { + get { return enableRectangularSelection; } + set { + if (enableRectangularSelection != value) { + enableRectangularSelection = value; + OnPropertyChanged("EnableRectangularSelection"); + } + } + } + + bool enableTextDragDrop = true; + + /// + /// Enable dragging text within the text area. + /// + [DefaultValue(true)] + public bool EnableTextDragDrop { + get { return enableTextDragDrop; } + set { + if (enableTextDragDrop != value) { + enableTextDragDrop = value; + OnPropertyChanged("EnableTextDragDrop"); + } + } + } + + bool enableVirtualSpace; + + /// + /// Gets/Sets whether the user can set the caret behind the line ending + /// (into "virtual space"). + /// Note that virtual space is always used (independent from this setting) + /// when doing rectangle selections. + /// + [DefaultValue(false)] + public virtual bool EnableVirtualSpace { + get { return enableVirtualSpace; } + set { + if (enableVirtualSpace != value) { + enableVirtualSpace = value; + OnPropertyChanged("EnableVirtualSpace"); + } + } + } + + bool enableImeSupport = true; + + /// + /// Gets/Sets whether the support for Input Method Editors (IME) + /// for non-alphanumeric scripts (Chinese, Japanese, Korean, ...) is enabled. + /// + [DefaultValue(true)] + public virtual bool EnableImeSupport { + get { return enableImeSupport; } + set { + if (enableImeSupport != value) { + enableImeSupport = value; + OnPropertyChanged("EnableImeSupport"); + } + } + } + + bool showColumnRuler = false; + + /// + /// Gets/Sets whether the column ruler should be shown. + /// + [DefaultValue(false)] + public virtual bool ShowColumnRuler { + get { return showColumnRuler; } + set { + if (showColumnRuler != value) { + showColumnRuler = value; + OnPropertyChanged("ShowColumnRuler"); + } + } + } + + int columnRulerPosition = 80; + + /// + /// Gets/Sets where the column ruler should be shown. + /// + [DefaultValue(80)] + public virtual int ColumnRulerPosition { + get { return columnRulerPosition; } + set { + if (columnRulerPosition != value) { + columnRulerPosition = value; + OnPropertyChanged("ColumnRulerPosition"); + } + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorWeakEventManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorWeakEventManager.cs new file mode 100644 index 000000000..87057ead4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditorWeakEventManager.cs @@ -0,0 +1,52 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using Tango.Scripting.Editors.Utils; +using System; + +namespace Tango.Scripting.Editors +{ + /// + /// Contains weak event managers for . + /// + public static class TextEditorWeakEventManager + { + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class DocumentChanged : WeakEventManagerBase + { + /// + protected override void StartListening(ITextEditorComponent source) + { + source.DocumentChanged += DeliverEvent; + } + + /// + protected override void StopListening(ITextEditorComponent source) + { + source.DocumentChanged -= DeliverEvent; + } + } + + /// + /// Weak event manager for the event. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public sealed class OptionChanged : WeakEventManagerBase + { + /// + protected override void StartListening(ITextEditorComponent source) + { + source.OptionChanged += DeliverEvent; + } + + /// + protected override void StopListening(ITextEditorComponent source) + { + source.OptionChanged -= DeliverEvent; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextViewPosition.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextViewPosition.cs new file mode 100644 index 000000000..ff7a78e31 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextViewPosition.cs @@ -0,0 +1,158 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors +{ + /// + /// Represents a text location with a visual column. + /// + public struct TextViewPosition : IEquatable + { + int line, column, visualColumn; + + /// + /// Gets/Sets Location. + /// + public TextLocation Location { + get { + return new TextLocation(line, column); + } + set { + line = value.Line; + column = value.Column; + } + } + + /// + /// Gets/Sets the line number. + /// + public int Line { + get { return line; } + set { line = value; } + } + + /// + /// Gets/Sets the (text) column number. + /// + public int Column { + get { return column; } + set { column = value; } + } + + /// + /// Gets/Sets the visual column number. + /// Can be -1 (meaning unknown visual column). + /// + public int VisualColumn { + get { return visualColumn; } + set { visualColumn = value; } + } + + /// + /// Creates a new TextViewPosition instance. + /// + public TextViewPosition(int line, int column, int visualColumn) + { + this.line = line; + this.column = column; + this.visualColumn = visualColumn; + } + + /// + /// Creates a new TextViewPosition instance. + /// + public TextViewPosition(int line, int column) + : this(line, column, -1) + { + } + + /// + /// Creates a new TextViewPosition instance. + /// + public TextViewPosition(TextLocation location, int visualColumn) + { + this.line = location.Line; + this.column = location.Column; + this.visualColumn = visualColumn; + } + + /// + /// Creates a new TextViewPosition instance. + /// + public TextViewPosition(TextLocation location) + : this(location, -1) + { + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, + "[TextViewPosition Line={0} Column={1} VisualColumn={2}]", + this.line, this.column, this.visualColumn); + } + + /// + /// Implicit conversion to . + /// + [Obsolete("Use the Location property instead of the implicit conversion to TextLocation")] + public static implicit operator TextLocation(TextViewPosition position) + { + return new TextLocation(position.Line, position.Column); + } + + #region Equals and GetHashCode implementation + // The code in this region is useful if you want to use this structure in collections. + // If you don't need it, you can just remove the region and the ": IEquatable" declaration. + + /// + public override bool Equals(object obj) + { + if (obj is TextViewPosition) + return Equals((TextViewPosition)obj); // use Equals method below + else + return false; + } + + /// + public override int GetHashCode() + { + int hashCode = 0; + unchecked { + hashCode += 1000000007 * Line.GetHashCode(); + hashCode += 1000000009 * Column.GetHashCode(); + hashCode += 1000000021 * VisualColumn.GetHashCode(); + } + return hashCode; + } + + /// + /// Equality test. + /// + public bool Equals(TextViewPosition other) + { + return this.Line == other.Line && this.Column == other.Column && this.VisualColumn == other.VisualColumn; + } + + /// + /// Equality test. + /// + public static bool operator ==(TextViewPosition left, TextViewPosition right) + { + return left.Equals(right); + } + + /// + /// Inequality test. + /// + public static bool operator !=(TextViewPosition left, TextViewPosition right) + { + return !(left.Equals(right)); // use operator == and negate result + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Theme.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Theme.xaml new file mode 100644 index 000000000..4847b6a10 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Theme.xaml @@ -0,0 +1,3066 @@ + + + + + + + #FF282828 + #FF202020 + #FF9BB1C5 + + + + #FF777777 + #FF2E2E2E + + #FF000000 + #FFFFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/Generic.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/Generic.xaml new file mode 100644 index 000000000..ce5cb39e1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/Generic.xaml @@ -0,0 +1,570 @@ + + + + + + + + + + + + #1E1E1E + #232323 + #3B3B3B + #E6E6E6 + #A0A0A0 + #2B91AF + #4EC9B0 + #3F8FD6 + #B5CE8A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/RightArrow.cur b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/RightArrow.cur new file mode 100644 index 000000000..5691efbaf Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Themes/RightArrow.cur differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Boxes.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Boxes.cs new file mode 100644 index 000000000..517948bae --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Boxes.cs @@ -0,0 +1,21 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Reuse the same instances for boxed booleans. + /// + static class Boxes + { + public static readonly object True = true; + public static readonly object False = false; + + public static object Box(bool value) + { + return value ? True : False; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/BusyManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/BusyManager.cs new file mode 100644 index 000000000..8551ff49e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/BusyManager.cs @@ -0,0 +1,55 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// This class is used to prevent stack overflows by representing a 'busy' flag + /// that prevents reentrance when another call is running. + /// However, using a simple 'bool busy' is not thread-safe, so we use a + /// thread-static BusyManager. + /// + static class BusyManager + { + public struct BusyLock : IDisposable + { + public static readonly BusyLock Failed = new BusyLock(null); + + readonly List objectList; + + public BusyLock(List objectList) + { + this.objectList = objectList; + } + + public bool Success { + get { return objectList != null; } + } + + public void Dispose() + { + if (objectList != null) { + objectList.RemoveAt(objectList.Count - 1); + } + } + } + + [ThreadStatic] static List _activeObjects; + + public static BusyLock Enter(object obj) + { + List activeObjects = _activeObjects; + if (activeObjects == null) + activeObjects = _activeObjects = new List(); + for (int i = 0; i < activeObjects.Count; i++) { + if (activeObjects[i] == obj) + return BusyLock.Failed; + } + activeObjects.Add(obj); + return new BusyLock(activeObjects); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CallbackOnDispose.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CallbackOnDispose.cs new file mode 100644 index 000000000..a6eebf2f1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CallbackOnDispose.cs @@ -0,0 +1,35 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Threading; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Invokes an action when it is disposed. + /// + /// + /// This class ensures the callback is invoked at most once, + /// even when Dispose is called on multiple threads. + /// + sealed class CallbackOnDispose : IDisposable + { + Action action; + + public CallbackOnDispose(Action action) + { + Debug.Assert(action != null); + this.action = action; + } + + public void Dispose() + { + Action a = Interlocked.Exchange(ref action, null); + if (a != null) { + a(); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CharRope.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CharRope.cs new file mode 100644 index 000000000..844ab9544 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CharRope.cs @@ -0,0 +1,172 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using System.Text; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Poor man's template specialization: extension methods for Rope<char>. + /// + public static class CharRope + { + /// + /// Creates a new rope from the specified text. + /// + public static Rope Create(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + return new Rope(InitFromString(text)); + } + + /// + /// Retrieves the text for a portion of the rope. + /// Runs in O(lg N + M), where M=. + /// + /// offset or length is outside the valid range. + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public static string ToString(this Rope rope, int startIndex, int length) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (length == 0) + return string.Empty; + char[] buffer = new char[length]; + rope.CopyTo(startIndex, buffer, 0, length); + return new string(buffer); + } + + /// + /// Retrieves the text for a portion of the rope and writes it to the specified string builder. + /// Runs in O(lg N + M), where M=. + /// + /// offset or length is outside the valid range. + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public static void WriteTo(this Rope rope, StringBuilder output, int startIndex, int length) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (output == null) + throw new ArgumentNullException("output"); + rope.VerifyRange(startIndex, length); + rope.root.WriteTo(startIndex, output, length); + } + + /// + /// Appends text to this rope. + /// Runs in O(lg N + M). + /// + /// newElements is null. + public static void AddText(this Rope rope, string text) + { + InsertText(rope, rope.Length, text); + } + + /// + /// Inserts text into this rope. + /// Runs in O(lg N + M). + /// + /// newElements is null. + /// index or length is outside the valid range. + public static void InsertText(this Rope rope, int index, string text) + { + if (rope == null) + throw new ArgumentNullException("rope"); + rope.InsertRange(index, text.ToCharArray(), 0, text.Length); + /*if (index < 0 || index > rope.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + rope.Length.ToString(CultureInfo.InvariantCulture)); + } + if (text == null) + throw new ArgumentNullException("text"); + if (text.Length == 0) + return; + rope.root = rope.root.Insert(index, text); + rope.OnChanged();*/ + } + + internal static RopeNode InitFromString(string text) + { + if (text.Length == 0) { + return RopeNode.emptyRopeNode; + } + RopeNode node = RopeNode.CreateNodes(text.Length); + FillNode(node, text, 0); + return node; + } + + static void FillNode(RopeNode node, string text, int start) + { + if (node.contents != null) { + text.CopyTo(start, node.contents, 0, node.length); + } else { + FillNode(node.left, text, start); + FillNode(node.right, text, start + node.left.length); + } + } + + internal static void WriteTo(this RopeNode node, int index, StringBuilder output, int count) + { + if (node.height == 0) { + if (node.contents == null) { + // function node + node.GetContentNode().WriteTo(index, output, count); + } else { + // leaf node: append data + output.Append(node.contents, index, count); + } + } else { + // concat node: do recursive calls + if (index + count <= node.left.length) { + node.left.WriteTo(index, output, count); + } else if (index >= node.left.length) { + node.right.WriteTo(index - node.left.length, output, count); + } else { + int amountInLeft = node.left.length - index; + node.left.WriteTo(index, output, amountInLeft); + node.right.WriteTo(0, output, count - amountInLeft); + } + } + } + + /// + /// Gets the index of the first occurrence of any element in the specified array. + /// + /// The target rope. + /// Array of characters being searched. + /// Start index of the search. + /// Length of the area to search. + /// The first index where any character was found; or -1 if no occurrence was found. + public static int IndexOfAny(this Rope rope, char[] anyOf, int startIndex, int length) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (anyOf == null) + throw new ArgumentNullException("anyOf"); + rope.VerifyRange(startIndex, length); + + while (length > 0) { + var entry = rope.FindNodeUsingCache(startIndex).UnsafePeek(); + char[] contents = entry.node.contents; + int startWithinNode = startIndex - entry.nodeStartIndex; + int nodeLength = Math.Min(entry.node.length, startWithinNode + length); + for (int i = startIndex - entry.nodeStartIndex; i < nodeLength; i++) { + char element = contents[i]; + foreach (char needle in anyOf) { + if (element == needle) + return entry.nodeStartIndex + i; + } + } + length -= nodeLength - startWithinNode; + startIndex = entry.nodeStartIndex + nodeLength; + } + return -1; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CompressingTreeList.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CompressingTreeList.cs new file mode 100644 index 000000000..42f5007c9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CompressingTreeList.cs @@ -0,0 +1,882 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// A IList{T} implementation that has efficient insertion and removal (in O(lg n) time) + /// and that saves memory by allocating only one node when a value is repeated in adjacent indices. + /// Based on this "compression", it also supports efficient InsertRange/SetRange/RemoveRange operations. + /// + /// + /// Current memory usage: 5*IntPtr.Size + 12 + sizeof(T) per node. + /// Use this class only if lots of adjacent values are identical (can share one node). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification = "It's an IList implementation")] + public sealed class CompressingTreeList : IList + { + // Further memory optimization: this tree could work without parent pointers. But that + // requires changing most of tree manipulating logic. + // Also possible is to remove the count field and calculate it as totalCount-left.totalCount-right.totalCount + // - but that would make tree manipulations more difficult to handle. + + #region Node definition + sealed class Node + { + internal Node left, right, parent; + internal bool color; + internal int count, totalCount; + internal T value; + + public Node(T value, int count) + { + this.value = value; + this.count = count; + this.totalCount = count; + } + + internal Node LeftMost { + get { + Node node = this; + while (node.left != null) + node = node.left; + return node; + } + } + + internal Node RightMost { + get { + Node node = this; + while (node.right != null) + node = node.right; + return node; + } + } + + /// + /// Gets the inorder predecessor of the node. + /// + internal Node Predecessor { + get { + if (left != null) { + return left.RightMost; + } else { + Node node = this; + Node oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a right subtree + } while (node != null && node.left == oldNode); + return node; + } + } + } + + /// + /// Gets the inorder successor of the node. + /// + internal Node Successor { + get { + if (right != null) { + return right.LeftMost; + } else { + Node node = this; + Node oldNode; + do { + oldNode = node; + node = node.parent; + // go up until we are coming out of a left subtree + } while (node != null && node.right == oldNode); + return node; + } + } + } + + public override string ToString() + { + return "[TotalCount=" + totalCount + " Count=" + count + " Value=" + value + "]"; + } + } + #endregion + + #region Fields and Constructor + readonly Func comparisonFunc; + Node root; + + /// + /// Creates a new CompressingTreeList instance. + /// + /// A function that checks two values for equality. If this + /// function returns true, a single node may be used to store the two values. + public CompressingTreeList(Func comparisonFunc) + { + if (comparisonFunc == null) + throw new ArgumentNullException("comparisonFunc"); + this.comparisonFunc = comparisonFunc; + } + #endregion + + #region InsertRange + /// + /// Inserts times at position + /// . + /// + public void InsertRange(int index, int count, T item) + { + if (index < 0 || index > this.Count) + throw new ArgumentOutOfRangeException("index", index, "Value must be between 0 and " + this.Count); + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "Value must not be negative"); + if (count == 0) + return; + unchecked { + if (this.Count + count < 0) + throw new OverflowException("Cannot insert elements: total number of elements must not exceed int.MaxValue."); + } + + if (root == null) { + root = new Node(item, count); + } else { + Node n = GetNode(ref index); + // check if we can put the value into the node n: + if (comparisonFunc(n.value, item)) { + n.count += count; + UpdateAugmentedData(n); + } else if (index == n.count) { + // this can only happen when appending at the end + Debug.Assert(n == root.RightMost); + InsertAsRight(n, new Node(item, count)); + } else if (index == 0) { + // insert before: + // maybe we can put the value in the previous node? + Node p = n.Predecessor; + if (p != null && comparisonFunc(p.value, item)) { + p.count += count; + UpdateAugmentedData(p); + } else { + InsertBefore(n, new Node(item, count)); + } + } else { + Debug.Assert(index > 0 && index < n.count); + // insert in the middle: + // split n into a new node and n + n.count -= index; + InsertBefore(n, new Node(n.value, index)); + // then insert the new item in between + InsertBefore(n, new Node(item, count)); + UpdateAugmentedData(n); + } + } + CheckProperties(); + } + + void InsertBefore(Node node, Node newNode) + { + if (node.left == null) { + InsertAsLeft(node, newNode); + } else { + InsertAsRight(node.left.RightMost, newNode); + } + } + #endregion + + #region RemoveRange + /// + /// Removes items starting at position + /// . + /// + public void RemoveRange(int index, int count) + { + if (index < 0 || index > this.Count) + throw new ArgumentOutOfRangeException("index", index, "Value must be between 0 and " + this.Count); + if (count < 0 || index + count > this.Count) + throw new ArgumentOutOfRangeException("count", count, "0 <= length, index(" + index + ")+count <= " + this.Count); + if (count == 0) + return; + + Node n = GetNode(ref index); + if (index + count < n.count) { + // just remove inside a single node + n.count -= count; + UpdateAugmentedData(n); + } else { + // keep only the part of n from 0 to index + Node firstNodeBeforeDeletedRange; + if (index > 0) { + count -= (n.count - index); + n.count = index; + UpdateAugmentedData(n); + firstNodeBeforeDeletedRange = n; + n = n.Successor; + } else { + Debug.Assert(index == 0); + firstNodeBeforeDeletedRange = n.Predecessor; + } + while (n != null && count >= n.count) { + count -= n.count; + Node s = n.Successor; + RemoveNode(n); + n = s; + } + if (count > 0) { + Debug.Assert(n != null && count < n.count); + n.count -= count; + UpdateAugmentedData(n); + } + if (n != null) { + Debug.Assert(n.Predecessor == firstNodeBeforeDeletedRange); + if (firstNodeBeforeDeletedRange != null && comparisonFunc(firstNodeBeforeDeletedRange.value, n.value)) { + firstNodeBeforeDeletedRange.count += n.count; + RemoveNode(n); + UpdateAugmentedData(firstNodeBeforeDeletedRange); + } + } + } + + CheckProperties(); + } + #endregion + + #region SetRange + /// + /// Sets indices starting at to + /// + /// + public void SetRange(int index, int count, T item) + { + RemoveRange(index, count); + InsertRange(index, count, item); + } + #endregion + + #region GetNode + Node GetNode(ref int index) + { + Node node = root; + while (true) { + if (node.left != null && index < node.left.totalCount) { + node = node.left; + } else { + if (node.left != null) { + index -= node.left.totalCount; + } + if (index < node.count || node.right == null) + return node; + index -= node.count; + node = node.right; + } + } + } + #endregion + + #region UpdateAugmentedData + void UpdateAugmentedData(Node node) + { + int totalCount = node.count; + if (node.left != null) totalCount += node.left.totalCount; + if (node.right != null) totalCount += node.right.totalCount; + if (node.totalCount != totalCount) { + node.totalCount = totalCount; + if (node.parent != null) + UpdateAugmentedData(node.parent); + } + } + #endregion + + #region IList implementation + /// + /// Gets or sets an item by index. + /// + public T this[int index] { + get { + if (index < 0 || index >= this.Count) + throw new ArgumentOutOfRangeException("index", index, "Value must be between 0 and " + (this.Count - 1)); + return GetNode(ref index).value; + } + set { + RemoveAt(index); + Insert(index, value); + } + } + + /// + /// Gets the number of items in the list. + /// + public int Count { + get { + if (root != null) + return root.totalCount; + else + return 0; + } + } + + bool ICollection.IsReadOnly { + get { + return false; + } + } + + /// + /// Gets the index of the specified . + /// + public int IndexOf(T item) + { + int index = 0; + if (root != null) { + Node n = root.LeftMost; + while (n != null) { + if (comparisonFunc(n.value, item)) + return index; + index += n.count; + n = n.Successor; + } + } + Debug.Assert(index == this.Count); + return -1; + } + + /// + /// Gets the the first index so that all values from the result index to + /// are equal. + /// + public int GetStartOfRun(int index) + { + if (index < 0 || index >= this.Count) + throw new ArgumentOutOfRangeException("index", index, "Value must be between 0 and " + (this.Count - 1)); + int indexInRun = index; + GetNode(ref indexInRun); + return index - indexInRun; + } + + /// + /// Gets the first index after so that the value at the result index is not + /// equal to the value at . + /// That is, this method returns the exclusive end index of the run of equal values. + /// + public int GetEndOfRun(int index) + { + if (index < 0 || index >= this.Count) + throw new ArgumentOutOfRangeException("index", index, "Value must be between 0 and " + (this.Count - 1)); + int indexInRun = index; + int runLength = GetNode(ref indexInRun).count; + return index - indexInRun + runLength; + } + + /// + /// Gets the number of elements after that have the same value as each other. + /// + [Obsolete("This method may be confusing as it returns only the remaining run length after index. " + + "Use GetStartOfRun/GetEndOfRun instead.")] + public int GetRunLength(int index) + { + if (index < 0 || index >= this.Count) + throw new ArgumentOutOfRangeException("index", index, "Value must be between 0 and " + (this.Count - 1)); + return GetNode(ref index).count - index; + } + + /// + /// Applies the conversion function to all elements in this CompressingTreeList. + /// + public void Transform(Func converter) + { + if (root == null) + return; + Node prevNode = null; + for (Node n = root.LeftMost; n != null; n = n.Successor) { + n.value = converter(n.value); + if (prevNode != null && comparisonFunc(prevNode.value, n.value)) { + n.count += prevNode.count; + UpdateAugmentedData(n); + RemoveNode(prevNode); + } + prevNode = n; + } + } + + + /// + /// Inserts the specified at + /// + public void Insert(int index, T item) + { + InsertRange(index, 1, item); + } + + /// + /// Removes one item at + /// + public void RemoveAt(int index) + { + RemoveRange(index, 1); + } + + /// + /// Adds the specified to the end of the list. + /// + public void Add(T item) + { + InsertRange(this.Count, 1, item); + } + + /// + /// Removes all items from this list. + /// + public void Clear() + { + root = null; + } + + /// + /// Gets whether this list contains the specified item. + /// + public bool Contains(T item) + { + return IndexOf(item) >= 0; + } + + /// + /// Copies all items in this list to the specified array. + /// + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (array.Length < this.Count) + throw new ArgumentException("The array is too small", "array"); + if (arrayIndex < 0 || arrayIndex + this.Count > array.Length) + throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - this.Count)); + foreach (T v in this) { + array[arrayIndex++] = v; + } + } + + /// + /// Removes the specified item from this list. + /// + public bool Remove(T item) + { + int index = IndexOf(item); + if (index >= 0) { + RemoveAt(index); + return true; + } else { + return false; + } + } + #endregion + + #region IEnumerable + /// + /// Gets an enumerator for this list. + /// + public IEnumerator GetEnumerator() + { + if (root != null) { + Node n = root.LeftMost; + while (n != null) { + for (int i = 0; i < n.count; i++) { + yield return n.value; + } + n = n.Successor; + } + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region Red/Black Tree + internal const bool RED = true; + internal const bool BLACK = false; + + void InsertAsLeft(Node parentNode, Node newNode) + { + Debug.Assert(parentNode.left == null); + parentNode.left = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAugmentedData(parentNode); + FixTreeOnInsert(newNode); + } + + void InsertAsRight(Node parentNode, Node newNode) + { + Debug.Assert(parentNode.right == null); + parentNode.right = newNode; + newNode.parent = parentNode; + newNode.color = RED; + UpdateAugmentedData(parentNode); + FixTreeOnInsert(newNode); + } + + void FixTreeOnInsert(Node node) + { + Debug.Assert(node != null); + Debug.Assert(node.color == RED); + Debug.Assert(node.left == null || node.left.color == BLACK); + Debug.Assert(node.right == null || node.right.color == BLACK); + + Node parentNode = node.parent; + if (parentNode == null) { + // we inserted in the root -> the node must be black + // since this is a root node, making the node black increments the number of black nodes + // on all paths by one, so it is still the same for all paths. + node.color = BLACK; + return; + } + if (parentNode.color == BLACK) { + // if the parent node where we inserted was black, our red node is placed correctly. + // since we inserted a red node, the number of black nodes on each path is unchanged + // -> the tree is still balanced + return; + } + // parentNode is red, so there is a conflict here! + + // because the root is black, parentNode is not the root -> there is a grandparent node + Node grandparentNode = parentNode.parent; + Node uncleNode = Sibling(parentNode); + if (uncleNode != null && uncleNode.color == RED) { + parentNode.color = BLACK; + uncleNode.color = BLACK; + grandparentNode.color = RED; + FixTreeOnInsert(grandparentNode); + return; + } + // now we know: parent is red but uncle is black + // First rotation: + if (node == parentNode.right && parentNode == grandparentNode.left) { + RotateLeft(parentNode); + node = node.left; + } else if (node == parentNode.left && parentNode == grandparentNode.right) { + RotateRight(parentNode); + node = node.right; + } + // because node might have changed, reassign variables: + parentNode = node.parent; + grandparentNode = parentNode.parent; + + // Now recolor a bit: + parentNode.color = BLACK; + grandparentNode.color = RED; + // Second rotation: + if (node == parentNode.left && parentNode == grandparentNode.left) { + RotateRight(grandparentNode); + } else { + // because of the first rotation, this is guaranteed: + Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); + RotateLeft(grandparentNode); + } + } + + void RemoveNode(Node removedNode) + { + if (removedNode.left != null && removedNode.right != null) { + // replace removedNode with it's in-order successor + + Node leftMost = removedNode.right.LeftMost; + RemoveNode(leftMost); // remove leftMost from its current location + + // and overwrite the removedNode with it + ReplaceNode(removedNode, leftMost); + leftMost.left = removedNode.left; + if (leftMost.left != null) leftMost.left.parent = leftMost; + leftMost.right = removedNode.right; + if (leftMost.right != null) leftMost.right.parent = leftMost; + leftMost.color = removedNode.color; + + UpdateAugmentedData(leftMost); + if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent); + return; + } + + // now either removedNode.left or removedNode.right is null + // get the remaining child + Node parentNode = removedNode.parent; + Node childNode = removedNode.left ?? removedNode.right; + ReplaceNode(removedNode, childNode); + if (parentNode != null) UpdateAugmentedData(parentNode); + if (removedNode.color == BLACK) { + if (childNode != null && childNode.color == RED) { + childNode.color = BLACK; + } else { + FixTreeOnDelete(childNode, parentNode); + } + } + } + + void FixTreeOnDelete(Node node, Node parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (parentNode == null) + return; + + // warning: node may be null + Node sibling = Sibling(node, parentNode); + if (sibling.color == RED) { + parentNode.color = RED; + sibling.color = BLACK; + if (node == parentNode.left) { + RotateLeft(parentNode); + } else { + RotateRight(parentNode); + } + + sibling = Sibling(node, parentNode); // update value of sibling after rotation + } + + if (parentNode.color == BLACK + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + FixTreeOnDelete(parentNode, parentNode.parent); + return; + } + + if (parentNode.color == RED + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + parentNode.color = BLACK; + return; + } + + if (node == parentNode.left && + sibling.color == BLACK && + GetColor(sibling.left) == RED && + GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + sibling.left.color = BLACK; + RotateRight(sibling); + } + else if (node == parentNode.right && + sibling.color == BLACK && + GetColor(sibling.right) == RED && + GetColor(sibling.left) == BLACK) + { + sibling.color = RED; + sibling.right.color = BLACK; + RotateLeft(sibling); + } + sibling = Sibling(node, parentNode); // update value of sibling after rotation + + sibling.color = parentNode.color; + parentNode.color = BLACK; + if (node == parentNode.left) { + if (sibling.right != null) { + Debug.Assert(sibling.right.color == RED); + sibling.right.color = BLACK; + } + RotateLeft(parentNode); + } else { + if (sibling.left != null) { + Debug.Assert(sibling.left.color == RED); + sibling.left.color = BLACK; + } + RotateRight(parentNode); + } + } + + void ReplaceNode(Node replacedNode, Node newNode) + { + if (replacedNode.parent == null) { + Debug.Assert(replacedNode == root); + root = newNode; + } else { + if (replacedNode.parent.left == replacedNode) + replacedNode.parent.left = newNode; + else + replacedNode.parent.right = newNode; + } + if (newNode != null) { + newNode.parent = replacedNode.parent; + } + replacedNode.parent = null; + } + + void RotateLeft(Node p) + { + // let q be p's right child + Node q = p.right; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's right child to be q's left child + p.right = q.left; + if (p.right != null) p.right.parent = p; + // set q's left child to be p + q.left = p; + p.parent = q; + UpdateAugmentedData(p); + UpdateAugmentedData(q); + } + + void RotateRight(Node p) + { + // let q be p's left child + Node q = p.left; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's left child to be q's right child + p.left = q.right; + if (p.left != null) p.left.parent = p; + // set q's right child to be p + q.right = p; + p.parent = q; + UpdateAugmentedData(p); + UpdateAugmentedData(q); + } + + static Node Sibling(Node node) + { + if (node == node.parent.left) + return node.parent.right; + else + return node.parent.left; + } + + static Node Sibling(Node node, Node parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (node == parentNode.left) + return parentNode.right; + else + return parentNode.left; + } + + static bool GetColor(Node node) + { + return node != null ? node.color : BLACK; + } + #endregion + + #region CheckProperties + [Conditional("DATACONSISTENCYTEST")] + internal void CheckProperties() + { + #if DEBUG + if (root != null) { + CheckProperties(root); + + // check red-black property: + int blackCount = -1; + CheckNodeProperties(root, null, RED, 0, ref blackCount); + + // ensure that the tree is compressed: + Node p = root.LeftMost; + Node n = p.Successor; + while (n != null) { + Debug.Assert(!comparisonFunc(p.value, n.value)); + p = n; + n = p.Successor; + } + } + #endif + } + + #if DEBUG + void CheckProperties(Node node) + { + Debug.Assert(node.count > 0); + int totalCount = node.count; + if (node.left != null) { + CheckProperties(node.left); + totalCount += node.left.totalCount; + } + if (node.right != null) { + CheckProperties(node.right); + totalCount += node.right.totalCount; + } + Debug.Assert(node.totalCount == totalCount); + } + + /* + 1. A node is either red or black. + 2. The root is black. + 3. All leaves are black. (The leaves are the NIL children.) + 4. Both children of every red node are black. (So every red node must have a black parent.) + 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) + */ + void CheckNodeProperties(Node node, Node parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) + { + if (node == null) return; + + Debug.Assert(node.parent == parentNode); + + if (parentColor == RED) { + Debug.Assert(node.color == BLACK); + } + if (node.color == BLACK) { + blackCount++; + } + if (node.left == null && node.right == null) { + // node is a leaf node: + if (expectedBlackCount == -1) + expectedBlackCount = blackCount; + else + Debug.Assert(expectedBlackCount == blackCount); + } + CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); + CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); + } + #endif + #endregion + + #region GetTreeAsString + internal string GetTreeAsString() + { + #if DEBUG + if (root == null) + return ""; + StringBuilder b = new StringBuilder(); + AppendTreeToString(root, b, 0); + return b.ToString(); + #else + return "Not available in release build."; + #endif + } + + #if DEBUG + static void AppendTreeToString(Node node, StringBuilder b, int indent) + { + if (node.color == RED) + b.Append("RED "); + else + b.Append("BLACK "); + b.AppendLine(node.ToString()); + indent += 2; + if (node.left != null) { + b.Append(' ', indent); + b.Append("L: "); + AppendTreeToString(node.left, b, indent); + } + if (node.right != null) { + b.Append(' ', indent); + b.Append("R: "); + AppendTreeToString(node.right, b, indent); + } + } + #endif + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Constants.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Constants.cs new file mode 100644 index 000000000..0344ada80 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Constants.cs @@ -0,0 +1,15 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Utils +{ + static class Constants + { + /// + /// Multiply with this constant to convert from points to device-independent pixels. + /// + public const double PixelPerPoint = 4 / 3.0; + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/DelayedEvents.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/DelayedEvents.cs new file mode 100644 index 000000000..8afc9f2ee --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/DelayedEvents.cs @@ -0,0 +1,48 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Maintains a list of delayed events to raise. + /// + sealed class DelayedEvents + { + struct EventCall + { + EventHandler handler; + object sender; + EventArgs e; + + public EventCall(EventHandler handler, object sender, EventArgs e) + { + this.handler = handler; + this.sender = sender; + this.e = e; + } + + public void Call() + { + handler(sender, e); + } + } + + Queue eventCalls = new Queue(); + + public void DelayedRaise(EventHandler handler, object sender, EventArgs e) + { + if (handler != null) { + eventCalls.Enqueue(new EventCall(handler, sender, e)); + } + } + + public void RaiseEvents() + { + while (eventCalls.Count > 0) + eventCalls.Dequeue().Call(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Deque.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Deque.cs new file mode 100644 index 000000000..66496d68f --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Deque.cs @@ -0,0 +1,174 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Double-ended queue. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + [Serializable] + public sealed class Deque : ICollection + { + T[] arr = Empty.Array; + int size, head, tail; + + /// + public int Count { + get { return size; } + } + + /// + public void Clear() + { + arr = Empty.Array; + size = 0; + head = 0; + tail = 0; + } + + /// + /// Gets/Sets an element inside the deque. + /// + public T this[int index] { + get { + ThrowUtil.CheckInRangeInclusive(index, "index", 0, size - 1); + return arr[(head + index) % arr.Length]; + } + set { + ThrowUtil.CheckInRangeInclusive(index, "index", 0, size - 1); + arr[(head + index) % arr.Length] = value; + } + } + + /// + /// Adds an element to the end of the deque. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PushBack")] + public void PushBack(T item) + { + if (size == arr.Length) + SetCapacity(Math.Max(4, arr.Length * 2)); + arr[tail++] = item; + if (tail == arr.Length) tail = 0; + size++; + } + + /// + /// Pops an element from the end of the deque. + /// + public T PopBack() + { + if (size == 0) + throw new InvalidOperationException(); + if (tail == 0) + tail = arr.Length - 1; + else + tail--; + T val = arr[tail]; + arr[tail] = default(T); // allow GC to collect the element + size--; + return val; + } + + /// + /// Adds an element to the front of the deque. + /// + public void PushFront(T item) + { + if (size == arr.Length) + SetCapacity(Math.Max(4, arr.Length * 2)); + if (head == 0) + head = arr.Length - 1; + else + head--; + arr[head] = item; + size++; + } + + /// + /// Pops an element from the end of the deque. + /// + public T PopFront() + { + if (size == 0) + throw new InvalidOperationException(); + T val = arr[head]; + arr[head] = default(T); // allow GC to collect the element + head++; + if (head == arr.Length) head = 0; + size--; + return val; + } + + void SetCapacity(int capacity) + { + T[] newArr = new T[capacity]; + CopyTo(newArr, 0); + head = 0; + tail = (size == capacity) ? 0 : size; + arr = newArr; + } + + /// + public IEnumerator GetEnumerator() + { + if (head < tail) { + for (int i = head; i < tail; i++) + yield return arr[i]; + } else { + for (int i = head; i < arr.Length; i++) + yield return arr[i]; + for (int i = 0; i < tail; i++) + yield return arr[i]; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + bool ICollection.IsReadOnly { + get { return false; } + } + + void ICollection.Add(T item) + { + PushBack(item); + } + + /// + public bool Contains(T item) + { + EqualityComparer comparer = EqualityComparer.Default; + foreach (T element in this) + if (comparer.Equals(item, element)) + return true; + return false; + } + + /// + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (head < tail) { + Array.Copy(arr, head, array, arrayIndex, tail - head); + } else { + int num1 = arr.Length - head; + Array.Copy(arr, head, array, arrayIndex, num1); + Array.Copy(arr, 0, array, arrayIndex + num1, tail); + } + } + + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Empty.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Empty.cs new file mode 100644 index 000000000..1f890dd7a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Empty.cs @@ -0,0 +1,17 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.ObjectModel; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Provides immutable empty list instances. + /// + static class Empty + { + public static readonly T[] Array = new T[0]; + //public static readonly ReadOnlyCollection ReadOnlyCollection = new ReadOnlyCollection(Array); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ExtensionMethods.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ExtensionMethods.cs new file mode 100644 index 000000000..d9b00ed33 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ExtensionMethods.cs @@ -0,0 +1,214 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Xml; + +namespace Tango.Scripting.Editors.Utils +{ + static class ExtensionMethods + { + #region Epsilon / IsClose / CoerceValue + /// + /// Epsilon used for IsClose() implementations. + /// We can use up quite a few digits in front of the decimal point (due to visual positions being relative to document origin), + /// and there's no need to be too accurate (we're dealing with pixels here), + /// so we will use the value 0.01. + /// Previosly we used 1e-8 but that was causing issues: + /// http://community.sharpdevelop.net/forums/t/16048.aspx + /// + public const double Epsilon = 0.01; + + /// + /// Returns true if the doubles are close (difference smaller than 0.01). + /// + public static bool IsClose(this double d1, double d2) + { + if (d1 == d2) // required for infinities + return true; + return Math.Abs(d1 - d2) < Epsilon; + } + + /// + /// Returns true if the doubles are close (difference smaller than 0.01). + /// + public static bool IsClose(this Size d1, Size d2) + { + return IsClose(d1.Width, d2.Width) && IsClose(d1.Height, d2.Height); + } + + /// + /// Returns true if the doubles are close (difference smaller than 0.01). + /// + public static bool IsClose(this Vector d1, Vector d2) + { + return IsClose(d1.X, d2.X) && IsClose(d1.Y, d2.Y); + } + + /// + /// Forces the value to stay between mininum and maximum. + /// + /// minimum, if value is less than minimum. + /// Maximum, if value is greater than maximum. + /// Otherwise, value. + public static double CoerceValue(this double value, double minimum, double maximum) + { + return Math.Max(Math.Min(value, maximum), minimum); + } + + /// + /// Forces the value to stay between mininum and maximum. + /// + /// minimum, if value is less than minimum. + /// Maximum, if value is greater than maximum. + /// Otherwise, value. + public static int CoerceValue(this int value, int minimum, int maximum) + { + return Math.Max(Math.Min(value, maximum), minimum); + } + #endregion + + #region CreateTypeface + /// + /// Creates typeface from the framework element. + /// + public static Typeface CreateTypeface(this FrameworkElement fe) + { + return new Typeface((FontFamily)fe.GetValue(TextBlock.FontFamilyProperty), + (FontStyle)fe.GetValue(TextBlock.FontStyleProperty), + (FontWeight)fe.GetValue(TextBlock.FontWeightProperty), + (FontStretch)fe.GetValue(TextBlock.FontStretchProperty)); + } + #endregion + + #region AddRange / Sequence + public static void AddRange(this ICollection collection, IEnumerable elements) + { + foreach (T e in elements) + collection.Add(e); + } + + /// + /// Creates an IEnumerable with a single value. + /// + public static IEnumerable Sequence(T value) + { + yield return value; + } + #endregion + + #region XML reading + /// + /// Gets the value of the attribute, or null if the attribute does not exist. + /// + public static string GetAttributeOrNull(this XmlElement element, string attributeName) + { + XmlAttribute attr = element.GetAttributeNode(attributeName); + return attr != null ? attr.Value : null; + } + + /// + /// Gets the value of the attribute as boolean, or null if the attribute does not exist. + /// + public static bool? GetBoolAttribute(this XmlElement element, string attributeName) + { + XmlAttribute attr = element.GetAttributeNode(attributeName); + return attr != null ? (bool?)XmlConvert.ToBoolean(attr.Value) : null; + } + + /// + /// Gets the value of the attribute as boolean, or null if the attribute does not exist. + /// + public static bool? GetBoolAttribute(this XmlReader reader, string attributeName) + { + string attributeValue = reader.GetAttribute(attributeName); + if (attributeValue == null) + return null; + else + return XmlConvert.ToBoolean(attributeValue); + } + #endregion + + #region DPI independence + public static Rect TransformToDevice(this Rect rect, Visual visual) + { + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; + return Rect.Transform(rect, matrix); + } + + public static Rect TransformFromDevice(this Rect rect, Visual visual) + { + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; + return Rect.Transform(rect, matrix); + } + + public static Size TransformToDevice(this Size size, Visual visual) + { + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; + return new Size(size.Width * matrix.M11, size.Height * matrix.M22); + } + + public static Size TransformFromDevice(this Size size, Visual visual) + { + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; + return new Size(size.Width * matrix.M11, size.Height * matrix.M22); + } + + public static Point TransformToDevice(this Point point, Visual visual) + { + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; + return new Point(point.X * matrix.M11, point.Y * matrix.M22); + } + + public static Point TransformFromDevice(this Point point, Visual visual) + { + Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; + return new Point(point.X * matrix.M11, point.Y * matrix.M22); + } + #endregion + + #region System.Drawing <-> WPF conversions + public static System.Drawing.Point ToSystemDrawing(this Point p) + { + return new System.Drawing.Point((int)p.X, (int)p.Y); + } + + public static Point ToWpf(this System.Drawing.Point p) + { + return new Point(p.X, p.Y); + } + + public static Size ToWpf(this System.Drawing.Size s) + { + return new Size(s.Width, s.Height); + } + + public static Rect ToWpf(this System.Drawing.Rectangle rect) + { + return new Rect(rect.Location.ToWpf(), rect.Size.ToWpf()); + } + #endregion + + [Conditional("DEBUG")] + public static void CheckIsFrozen(Freezable f) + { + if (f != null && !f.IsFrozen) + Debug.WriteLine("Performance warning: Not frozen: " + f.ToString()); + } + + [Conditional("DEBUG")] + public static void Log(bool condition, string format, params object[] args) + { + if (condition) { + string output = DateTime.Now.ToString("hh:MM:ss") + ": " + string.Format(format, args) + Environment.NewLine + Environment.StackTrace; + Console.WriteLine(output); + Debug.WriteLine(output); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/FileReader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/FileReader.cs new file mode 100644 index 000000000..b44c589d7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/FileReader.cs @@ -0,0 +1,208 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; +using System.Text; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Class that can open text files with auto-detection of the encoding. + /// + public static class FileReader + { + /// + /// Gets if the given encoding is a Unicode encoding (UTF). + /// + /// + /// Returns true for UTF-7, UTF-8, UTF-16 LE, UTF-16 BE, UTF-32 LE and UTF-32 BE. + /// Returns false for all other encodings. + /// + public static bool IsUnicode(Encoding encoding) + { + if (encoding == null) + throw new ArgumentNullException("encoding"); + switch (encoding.CodePage) { + case 65000: // UTF-7 + case 65001: // UTF-8 + case 1200: // UTF-16 LE + case 1201: // UTF-16 BE + case 12000: // UTF-32 LE + case 12001: // UTF-32 BE + return true; + default: + return false; + } + } + + /// + /// Reads the content of the given stream. + /// + /// The stream to read. + /// The stream must support seeking and must be positioned at its beginning. + /// The encoding to use if the encoding cannot be auto-detected. + /// The file content as string. + public static string ReadFileContent(Stream stream, Encoding defaultEncoding) + { + using (StreamReader reader = OpenStream(stream, defaultEncoding)) { + return reader.ReadToEnd(); + } + } + + /// + /// Reads the content of the file. + /// + /// The file name. + /// The encoding to use if the encoding cannot be auto-detected. + /// The file content as string. + public static string ReadFileContent(string fileName, Encoding defaultEncoding) + { + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { + return ReadFileContent(fs, defaultEncoding); + } + } + + /// + /// Opens the specified file for reading. + /// + /// The file to open. + /// The encoding to use if the encoding cannot be auto-detected. + /// Returns a StreamReader that reads from the stream. Use + /// to get the encoding that was used. + public static StreamReader OpenFile(string fileName, Encoding defaultEncoding) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); + try { + return OpenStream(fs, defaultEncoding); + // don't use finally: the stream must be kept open until the StreamReader closes it + } catch { + fs.Dispose(); + throw; + } + } + + /// + /// Opens the specified stream for reading. + /// + /// The stream to open. + /// The encoding to use if the encoding cannot be auto-detected. + /// Returns a StreamReader that reads from the stream. Use + /// to get the encoding that was used. + public static StreamReader OpenStream(Stream stream, Encoding defaultEncoding) + { + if (stream == null) + throw new ArgumentNullException("stream"); + if (stream.Position != 0) + throw new ArgumentException("stream is not positioned at beginning.", "stream"); + if (defaultEncoding == null) + throw new ArgumentNullException("defaultEncoding"); + + if (stream.Length >= 2) { + // the autodetection of StreamReader is not capable of detecting the difference + // between ISO-8859-1 and UTF-8 without BOM. + int firstByte = stream.ReadByte(); + int secondByte = stream.ReadByte(); + switch ((firstByte << 8) | secondByte) { + case 0x0000: // either UTF-32 Big Endian or a binary file; use StreamReader + case 0xfffe: // Unicode BOM (UTF-16 LE or UTF-32 LE) + case 0xfeff: // UTF-16 BE BOM + case 0xefbb: // start of UTF-8 BOM + // StreamReader autodetection works + stream.Position = 0; + return new StreamReader(stream); + default: + return AutoDetect(stream, (byte)firstByte, (byte)secondByte, defaultEncoding); + } + } else { + if (defaultEncoding != null) { + return new StreamReader(stream, defaultEncoding); + } else { + return new StreamReader(stream); + } + } + } + + static StreamReader AutoDetect(Stream fs, byte firstByte, byte secondByte, Encoding defaultEncoding) + { + int max = (int)Math.Min(fs.Length, 500000); // look at max. 500 KB + const int ASCII = 0; + const int Error = 1; + const int UTF8 = 2; + const int UTF8Sequence = 3; + int state = ASCII; + int sequenceLength = 0; + byte b; + for (int i = 0; i < max; i++) { + if (i == 0) { + b = firstByte; + } else if (i == 1) { + b = secondByte; + } else { + b = (byte)fs.ReadByte(); + } + if (b < 0x80) { + // normal ASCII character + if (state == UTF8Sequence) { + state = Error; + break; + } + } else if (b < 0xc0) { + // 10xxxxxx : continues UTF8 byte sequence + if (state == UTF8Sequence) { + --sequenceLength; + if (sequenceLength < 0) { + state = Error; + break; + } else if (sequenceLength == 0) { + state = UTF8; + } + } else { + state = Error; + break; + } + } else if (b >= 0xc2 && b < 0xf5) { + // beginning of byte sequence + if (state == UTF8 || state == ASCII) { + state = UTF8Sequence; + if (b < 0xe0) { + sequenceLength = 1; // one more byte following + } else if (b < 0xf0) { + sequenceLength = 2; // two more bytes following + } else { + sequenceLength = 3; // three more bytes following + } + } else { + state = Error; + break; + } + } else { + // 0xc0, 0xc1, 0xf5 to 0xff are invalid in UTF-8 (see RFC 3629) + state = Error; + break; + } + } + fs.Position = 0; + switch (state) { + case ASCII: + case Error: + // when the file seems to be ASCII or non-UTF8, + // we read it using the user-specified encoding so it is saved again + // using that encoding. + if (IsUnicode(defaultEncoding)) { + // the file is not Unicode, so don't read it using Unicode even if the + // user has choosen Unicode as the default encoding. + + // If we don't do this, SD will end up always adding a Byte Order Mark + // to ASCII files. + defaultEncoding = Encoding.Default; // use system encoding instead + } + return new StreamReader(fs, defaultEncoding); + default: + return new StreamReader(fs); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ImmutableStack.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ImmutableStack.cs new file mode 100644 index 000000000..18a835fd2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ImmutableStack.cs @@ -0,0 +1,114 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// An immutable stack. + /// + /// Using 'foreach' on the stack will return the items from top to bottom (in the order they would be popped). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + [Serializable] + public sealed class ImmutableStack : IEnumerable + { + /// + /// Gets the empty stack instance. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "ImmutableStack is immutable")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static readonly ImmutableStack Empty = new ImmutableStack(); + + readonly T value; + readonly ImmutableStack next; + + private ImmutableStack() + { + } + + private ImmutableStack(T value, ImmutableStack next) + { + this.value = value; + this.next = next; + } + + /// + /// Pushes an item on the stack. This does not modify the stack itself, but returns a new + /// one with the value pushed. + /// + public ImmutableStack Push(T item) + { + return new ImmutableStack(item, this); + } + + /// + /// Gets the item on the top of the stack. + /// + /// The stack is empty. + public T Peek() + { + if (IsEmpty) + throw new InvalidOperationException("Operation not valid on empty stack."); + return value; + } + + internal T UnsafePeek() + { + Debug.Assert(!IsEmpty); + return value; + } + + /// + /// Gets the stack with the top item removed. + /// + /// The stack is empty. + public ImmutableStack Pop() + { + if (IsEmpty) + throw new InvalidOperationException("Operation not valid on empty stack."); + return next; + } + + /// + /// Gets if this stack is empty. + /// + public bool IsEmpty { + get { return next == null; } + } + + /// + /// Gets an enumerator that iterates through the stack top-to-bottom. + /// + public IEnumerator GetEnumerator() + { + ImmutableStack t = this; + while (!t.IsEmpty) { + yield return t.value; + t = t.next; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + public override string ToString() + { + StringBuilder b = new StringBuilder("[Stack"); + foreach (T val in this) { + b.Append(' '); + b.Append(val); + } + b.Append(']'); + return b.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/NullSafeCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/NullSafeCollection.cs new file mode 100644 index 000000000..8a37c54ae --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/NullSafeCollection.cs @@ -0,0 +1,31 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.ObjectModel; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// A collection that cannot contain null values. + /// + [Serializable] + public class NullSafeCollection : Collection where T : class + { + /// + protected override void InsertItem(int index, T item) + { + if (item == null) + throw new ArgumentNullException("item"); + base.InsertItem(index, item); + } + + /// + protected override void SetItem(int index, T item) + { + if (item == null) + throw new ArgumentNullException("item"); + base.SetItem(index, item); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ObserveAddRemoveCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ObserveAddRemoveCollection.cs new file mode 100644 index 000000000..091de0d91 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ObserveAddRemoveCollection.cs @@ -0,0 +1,63 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.ObjectModel; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// A collection where adding and removing items causes a callback. + /// It is valid for the onAdd callback to throw an exception - this will prevent the new item from + /// being added to the collection. + /// + sealed class ObserveAddRemoveCollection : Collection + { + readonly Action onAdd, onRemove; + + public ObserveAddRemoveCollection(Action onAdd, Action onRemove) + { + this.onAdd = onAdd; + this.onRemove = onRemove; + } + + protected override void ClearItems() + { + if (onRemove != null) { + foreach (T val in this) + onRemove(val); + } + base.ClearItems(); + } + + protected override void InsertItem(int index, T item) + { + if (onAdd != null) + onAdd(item); + base.InsertItem(index, item); + } + + protected override void RemoveItem(int index) + { + if (onRemove != null) + onRemove(this[index]); + base.RemoveItem(index); + } + + protected override void SetItem(int index, T item) + { + if (onRemove != null) + onRemove(this[index]); + try { + if (onAdd != null) + onAdd(item); + } catch { + // When adding the new item fails, just remove the old one + // (we cannot keep the old item since we already successfully called onRemove for it) + base.RemoveAt(index); + throw; + } + base.SetItem(index, item); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PixelSnapHelpers.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PixelSnapHelpers.cs new file mode 100644 index 000000000..abd859533 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PixelSnapHelpers.cs @@ -0,0 +1,92 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Windows; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Contains static helper methods for aligning stuff on a whole number of pixels. + /// + public static class PixelSnapHelpers + { + /// + /// Gets the pixel size on the screen containing visual. + /// This method does not take transforms on visual into account. + /// + public static Size GetPixelSize(Visual visual) + { + if (visual == null) + throw new ArgumentNullException("visual"); + PresentationSource source = PresentationSource.FromVisual(visual); + if (source != null) { + Matrix matrix = source.CompositionTarget.TransformFromDevice; + return new Size(matrix.M11, matrix.M22); + } else { + return new Size(1, 1); + } + } + + /// + /// Aligns on the next middle of a pixel. + /// + /// The value that should be aligned + /// The size of one pixel + public static double PixelAlign(double value, double pixelSize) + { + // 0 -> 0.5 + // 0.1 -> 0.5 + // 0.5 -> 0.5 + // 0.9 -> 0.5 + // 1 -> 1.5 + return pixelSize * (Math.Round((value / pixelSize) + 0.5) - 0.5); + } + + /// + /// Aligns the borders of rect on the middles of pixels. + /// + public static Rect PixelAlign(Rect rect, Size pixelSize) + { + rect.X = PixelAlign(rect.X, pixelSize.Width); + rect.Y = PixelAlign(rect.Y, pixelSize.Height); + rect.Width = Round(rect.Width, pixelSize.Width); + rect.Height = Round(rect.Height, pixelSize.Height); + return rect; + } + + /// + /// Rounds to whole number of pixels. + /// + public static Point Round(Point point, Size pixelSize) + { + return new Point(Round(point.X, pixelSize.Width), Round(point.Y, pixelSize.Height)); + } + + /// + /// Rounds val to whole number of pixels. + /// + public static Rect Round(Rect rect, Size pixelSize) + { + return new Rect(Round(rect.X, pixelSize.Width), Round(rect.Y, pixelSize.Height), + Round(rect.Width, pixelSize.Width), Round(rect.Height, pixelSize.Height)); + } + + /// + /// Rounds to a whole number of pixels. + /// + public static double Round(double value, double pixelSize) + { + return pixelSize * Math.Round(value / pixelSize); + } + + /// + /// Rounds to an whole odd number of pixels. + /// + public static double RoundToOdd(double value, double pixelSize) + { + return Round(value - pixelSize, pixelSize * 2) + pixelSize; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PropertyChangedWeakEventManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PropertyChangedWeakEventManager.cs new file mode 100644 index 000000000..9b70c8f54 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/PropertyChangedWeakEventManager.cs @@ -0,0 +1,26 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// WeakEventManager for INotifyPropertyChanged.PropertyChanged. + /// + public sealed class PropertyChangedWeakEventManager : WeakEventManagerBase + { + /// + protected override void StartListening(INotifyPropertyChanged source) + { + source.PropertyChanged += DeliverEvent; + } + + /// + protected override void StopListening(INotifyPropertyChanged source) + { + source.PropertyChanged -= DeliverEvent; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Rope.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Rope.cs new file mode 100644 index 000000000..d27edd199 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Rope.cs @@ -0,0 +1,789 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.Serialization; +using System.Text; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// A kind of List<T>, but more efficient for random insertions/removal. + /// Also has cheap Clone() and SubRope() implementations. + /// + /// + /// This class is not thread-safe: multiple concurrent write operations or writes concurrent to reads have undefined behaviour. + /// Concurrent reads, however, are safe. + /// However, clones of a rope are safe to use on other threads even though they share data with the original rope. + /// + [Serializable] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + public sealed class Rope : IList, ICloneable + { + internal RopeNode root; + + internal Rope(RopeNode root) + { + this.root = root; + root.CheckInvariants(); + } + + /// + /// Creates a new rope representing the empty string. + /// + public Rope() + { + // we'll construct the empty rope as a clone of an imaginary static empty rope + this.root = RopeNode.emptyRopeNode; + root.CheckInvariants(); + } + + /// + /// Creates a rope from the specified input. + /// This operation runs in O(N). + /// + /// input is null. + public Rope(IEnumerable input) + { + if (input == null) + throw new ArgumentNullException("input"); + Rope inputRope = input as Rope; + if (inputRope != null) { + // clone ropes instead of copying them + inputRope.root.Publish(); + this.root = inputRope.root; + } else { + string text = input as string; + if (text != null) { + // if a string is IEnumerable, then T must be char + ((Rope)(object)this).root = CharRope.InitFromString(text); + } else { + T[] arr = ToArray(input); + this.root = RopeNode.CreateFromArray(arr, 0, arr.Length); + } + } + this.root.CheckInvariants(); + } + + /// + /// Creates a rope from a part of the array. + /// This operation runs in O(N). + /// + /// input is null. + public Rope(T[] array, int arrayIndex, int count) + { + VerifyArrayWithRange(array, arrayIndex, count); + this.root = RopeNode.CreateFromArray(array, arrayIndex, count); + this.root.CheckInvariants(); + } + + /// + /// Creates a new rope that lazily initalizes its content. + /// + /// The length of the rope that will be lazily loaded. + /// + /// The callback that provides the content for this rope. + /// will be called exactly once when the content of this rope is first requested. + /// It must return a rope with the specified length. + /// Because the initializer function is not called when a rope is cloned, and such clones may be used on another threads, + /// it is possible for the initializer callback to occur on any thread. + /// + /// + /// Any modifications inside the rope will also cause the content to be initialized. + /// However, insertions at the beginning and the end, as well as inserting this rope into another or + /// using the method, allows constructions of larger ropes where parts are + /// lazily loaded. + /// However, even methods like Concat may sometimes cause the initializer function to be called, e.g. when + /// two short ropes are concatenated. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Rope(int length, Func> initializer) + { + if (initializer == null) + throw new ArgumentNullException("initializer"); + if (length < 0) + throw new ArgumentOutOfRangeException("length", length, "Length must not be negative"); + if (length == 0) { + this.root = RopeNode.emptyRopeNode; + } else { + this.root = new FunctionNode(length, initializer); + } + this.root.CheckInvariants(); + } + + static T[] ToArray(IEnumerable input) + { + T[] arr = input as T[]; + return arr ?? input.ToArray(); + } + + /// + /// Clones the rope. + /// This operation runs in linear time to the number of rope nodes touched since the last clone was created. + /// If you count the per-node cost to the operation modifying the rope (doing this doesn't increase the complexity of the modification operations); + /// the remainder of Clone() runs in O(1). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public Rope Clone() + { + // The Publish() call actually modifies this rope instance; but this modification is thread-safe + // as long as the tree structure doesn't change during the operation. + root.Publish(); + return new Rope(root); + } + + object ICloneable.Clone() + { + return this.Clone(); + } + + /// + /// Resets the rope to an empty list. + /// Runs in O(1). + /// + public void Clear() + { + root = RopeNode.emptyRopeNode; + OnChanged(); + } + + /// + /// Gets the length of the rope. + /// Runs in O(1). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public int Length { + get { return root.length; } + } + + /// + /// Gets the length of the rope. + /// Runs in O(1). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public int Count { + get { return root.length; } + } + + /// + /// Inserts another rope into this rope. + /// Runs in O(lg N + lg M), plus a per-node cost as if newElements.Clone() was called. + /// + /// newElements is null. + /// index or length is outside the valid range. + public void InsertRange(int index, Rope newElements) + { + if (index < 0 || index > this.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + if (newElements == null) + throw new ArgumentNullException("newElements"); + newElements.root.Publish(); + root = root.Insert(index, newElements.root); + OnChanged(); + } + + /// + /// Inserts new elemetns into this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// + /// newElements is null. + /// index or length is outside the valid range. + public void InsertRange(int index, IEnumerable newElements) + { + if (newElements == null) + throw new ArgumentNullException("newElements"); + Rope newElementsRope = newElements as Rope; + if (newElementsRope != null) { + InsertRange(index, newElementsRope); + } else { + T[] arr = ToArray(newElements); + InsertRange(index, arr, 0, arr.Length); + } + } + + /// + /// Inserts new elements into this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// + /// newElements is null. + /// index or length is outside the valid range. + public void InsertRange(int index, T[] array, int arrayIndex, int count) + { + if (index < 0 || index > this.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + VerifyArrayWithRange(array, arrayIndex, count); + if (count > 0) { + root = root.Insert(index, array, arrayIndex, count); + OnChanged(); + } + } + + /// + /// Appends multiple elements to the end of this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// + /// newElements is null. + public void AddRange(IEnumerable newElements) + { + InsertRange(this.Length, newElements); + } + + /// + /// Appends another rope to the end of this rope. + /// Runs in O(lg N + lg M), plus a per-node cost as if newElements.Clone() was called. + /// + /// newElements is null. + public void AddRange(Rope newElements) + { + InsertRange(this.Length, newElements); + } + + /// + /// Appends new elements to the end of this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// + /// array is null. + public void AddRange(T[] array, int arrayIndex, int count) + { + InsertRange(this.Length, array, arrayIndex, count); + } + + /// + /// Removes a range of elements from the rope. + /// Runs in O(lg N). + /// + /// offset or length is outside the valid range. + public void RemoveRange(int index, int count) + { + VerifyRange(index, count); + if (count > 0) { + root = root.RemoveRange(index, count); + OnChanged(); + } + } + + /// + /// Copies a range of the specified array into the rope, overwriting existing elements. + /// Runs in O(lg N + M). + /// + public void SetRange(int index, T[] array, int arrayIndex, int count) + { + VerifyRange(index, count); + VerifyArrayWithRange(array, arrayIndex, count); + if (count > 0) { + root = root.StoreElements(index, array, arrayIndex, count); + OnChanged(); + } + } + + /// + /// Creates a new rope and initializes it with a part of this rope. + /// Runs in O(lg N) plus a per-node cost as if this.Clone() was called. + /// + /// offset or length is outside the valid range. + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public Rope GetRange(int index, int count) + { + VerifyRange(index, count); + Rope newRope = Clone(); + int endIndex = index + count; + newRope.RemoveRange(endIndex, newRope.Length - endIndex); + newRope.RemoveRange(0, index); + return newRope; + } + + /* + #region Equality + /// + /// Gets whether the two ropes have the same content. + /// Runs in O(N + M). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public bool Equals(Rope other) + { + if (other == null) + return false; + // quick detection for ropes that are clones of each other: + if (other.root == this.root) + return true; + if (other.Length != this.Length) + return false; + using (RopeTextReader a = new RopeTextReader(this, false)) { + using (RopeTextReader b = new RopeTextReader(other, false)) { + int charA, charB; + do { + charA = a.Read(); + charB = b.Read(); + if (charA != charB) + return false; + } while (charA != -1); + return true; + } + } + } + + /// + /// Gets whether two ropes have the same content. + /// Runs in O(N + M). + /// + public override bool Equals(object obj) + { + return Equals(obj as Rope); + } + + /// + /// Calculates the hash code of the rope's content. + /// Runs in O(N). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public override int GetHashCode() + { + int hashcode = 0; + using (RopeTextReader reader = new RopeTextReader(this, false)) { + unchecked { + int val; + while ((val = reader.Read()) != -1) { + hashcode = hashcode * 31 + val; + } + } + } + return hashcode; + } + #endregion + */ + + /// + /// Concatenates two ropes. The input ropes are not modified. + /// Runs in O(lg N + lg M). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static Rope Concat(Rope left, Rope right) + { + if (left == null) + throw new ArgumentNullException("left"); + if (right == null) + throw new ArgumentNullException("right"); + left.root.Publish(); + right.root.Publish(); + return new Rope(RopeNode.Concat(left.root, right.root)); + } + + /// + /// Concatenates multiple ropes. The input ropes are not modified. + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static Rope Concat(params Rope[] ropes) + { + if (ropes == null) + throw new ArgumentNullException("ropes"); + Rope result = new Rope(); + foreach (Rope r in ropes) + result.AddRange(r); + return result; + } + + #region Caches / Changed event + internal struct RopeCacheEntry + { + internal readonly RopeNode node; + internal readonly int nodeStartIndex; + + internal RopeCacheEntry(RopeNode node, int nodeStartOffset) + { + this.node = node; + this.nodeStartIndex = nodeStartOffset; + } + + internal bool IsInside(int offset) + { + return offset >= nodeStartIndex && offset < nodeStartIndex + node.length; + } + } + + // cached pointer to 'last used node', used to speed up accesses by index that are close together + [NonSerialized] + volatile ImmutableStack lastUsedNodeStack; + + internal void OnChanged() + { + lastUsedNodeStack = null; + + root.CheckInvariants(); + } + #endregion + + #region GetChar / SetChar + /// + /// Gets/Sets a single character. + /// Runs in O(lg N) for random access. Sequential read-only access benefits from a special optimization and runs in amortized O(1). + /// + /// Offset is outside the valid range (0 to Length-1). + /// + /// The getter counts as a read access and may be called concurrently to other read accesses. + /// + public T this[int index] { + get { + // use unsigned integers - this way negative values for index overflow and can be tested for with the same check + if (unchecked((uint)index >= (uint)this.Length)) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + RopeCacheEntry entry = FindNodeUsingCache(index).UnsafePeek(); + return entry.node.contents[index - entry.nodeStartIndex]; + } + set { + if (index < 0 || index >= this.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + root = root.SetElement(index, value); + OnChanged(); + /* Here's a try at implementing the setter using the cached node stack (UNTESTED code!). + * However I don't use the code because it's complicated and doesn't integrate correctly with change notifications. + * Instead, I'll use the much easier to understand recursive solution. + * Oh, and it also doesn't work correctly with function nodes. + ImmutableStack nodeStack = FindNodeUsingCache(offset); + RopeCacheEntry entry = nodeStack.Peek(); + if (!entry.node.isShared) { + entry.node.contents[offset - entry.nodeStartOffset] = value; + // missing: clear the caches except for the node stack cache (e.g. ToString() cache?) + } else { + RopeNode oldNode = entry.node; + RopeNode newNode = oldNode.Clone(); + newNode.contents[offset - entry.nodeStartOffset] = value; + for (nodeStack = nodeStack.Pop(); !nodeStack.IsEmpty; nodeStack = nodeStack.Pop()) { + RopeNode parentNode = nodeStack.Peek().node; + RopeNode newParentNode = parentNode.CloneIfShared(); + if (newParentNode.left == oldNode) { + newParentNode.left = newNode; + } else { + Debug.Assert(newParentNode.right == oldNode); + newParentNode.right = newNode; + } + if (parentNode == newParentNode) { + // we were able to change the existing node (it was not shared); + // there's no reason to go further upwards + ClearCacheOnModification(); + return; + } else { + oldNode = parentNode; + newNode = newParentNode; + } + } + // we reached the root of the rope. + Debug.Assert(root == oldNode); + root = newNode; + ClearCacheOnModification(); + }*/ + } + } + + internal ImmutableStack FindNodeUsingCache(int index) + { + Debug.Assert(index >= 0 && index < this.Length); + + // thread safety: fetch stack into local variable + ImmutableStack stack = lastUsedNodeStack; + ImmutableStack oldStack = stack; + + if (stack == null) { + stack = ImmutableStack.Empty.Push(new RopeCacheEntry(root, 0)); + } + while (!stack.UnsafePeek().IsInside(index)) + stack = stack.Pop(); + while (true) { + RopeCacheEntry entry = stack.UnsafePeek(); + // check if we've reached a leaf or function node + if (entry.node.height == 0) { + if (entry.node.contents == null) { + // this is a function node - go down into its subtree + entry = new RopeCacheEntry(entry.node.GetContentNode(), entry.nodeStartIndex); + // entry is now guaranteed NOT to be another function node + } + if (entry.node.contents != null) { + // this is a node containing actual content, so we're done + break; + } + } + // go down towards leaves + if (index - entry.nodeStartIndex >= entry.node.left.length) + stack = stack.Push(new RopeCacheEntry(entry.node.right, entry.nodeStartIndex + entry.node.left.length)); + else + stack = stack.Push(new RopeCacheEntry(entry.node.left, entry.nodeStartIndex)); + } + + // write back stack to volatile cache variable + // (in multithreaded access, it doesn't matter which of the threads wins - it's just a cache) + if (oldStack != stack) { + // no need to write when we the cache variable didn't change + lastUsedNodeStack = stack; + } + + // this method guarantees that it finds a leaf node + Debug.Assert(stack.Peek().node.contents != null); + return stack; + } + #endregion + + #region ToString / WriteTo + internal void VerifyRange(int startIndex, int length) + { + if (startIndex < 0 || startIndex > this.Length) { + throw new ArgumentOutOfRangeException("startIndex", startIndex, "0 <= startIndex <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + if (length < 0 || startIndex + length > this.Length) { + throw new ArgumentOutOfRangeException("length", length, "0 <= length, startIndex(" + startIndex + ")+length <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + internal static void VerifyArrayWithRange(T[] array, int arrayIndex, int count) + { + if (array == null) + throw new ArgumentNullException("array"); + if (arrayIndex < 0 || arrayIndex > array.Length) { + throw new ArgumentOutOfRangeException("startIndex", arrayIndex, "0 <= arrayIndex <= " + array.Length.ToString(CultureInfo.InvariantCulture)); + } + if (count < 0 || arrayIndex + count > array.Length) { + throw new ArgumentOutOfRangeException("count", count, "0 <= length, arrayIndex(" + arrayIndex + ")+count <= " + array.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + /// + /// Creates a string from the rope. Runs in O(N). + /// + /// A string consisting of all elements in the rope as comma-separated list in {}. + /// As a special case, Rope<char> will return its contents as string without any additional separators or braces, + /// so it can be used like StringBuilder.ToString(). + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public override string ToString() + { + Rope charRope = this as Rope; + if (charRope != null) { + return charRope.ToString(0, this.Length); + } else { + StringBuilder b = new StringBuilder(); + foreach (T element in this) { + if (b.Length == 0) + b.Append('{'); + else + b.Append(", "); + b.Append(element.ToString()); + } + b.Append('}'); + return b.ToString(); + } + } + + internal string GetTreeAsString() + { + #if DEBUG + return root.GetTreeAsString(); + #else + return "Not available in release build."; + #endif + } + #endregion + + bool ICollection.IsReadOnly { + get { return false; } + } + + /// + /// Finds the first occurance of item. + /// Runs in O(N). + /// + /// The index of the first occurance of item, or -1 if it cannot be found. + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public int IndexOf(T item) + { + var comparer = EqualityComparer.Default; + int index = 0; + foreach (T element in this) { + if (comparer.Equals(item, element)) + return index; + index++; + } + return -1; + } + + /// + /// Inserts the item at the specified index in the rope. + /// Runs in O(lg N). + /// + public void Insert(int index, T item) + { + InsertRange(index, new[] { item }, 0, 1); + } + + /// + /// Removes a single item from the rope. + /// Runs in O(lg N). + /// + public void RemoveAt(int index) + { + RemoveRange(index, 1); + } + + /// + /// Appends the item at the end of the rope. + /// Runs in O(lg N). + /// + public void Add(T item) + { + InsertRange(this.Length, new[] { item }, 0, 1); + } + + /// + /// Searches the item in the rope. + /// Runs in O(N). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public bool Contains(T item) + { + return IndexOf(item) >= 0; + } + + /// + /// Copies the whole content of the rope into the specified array. + /// Runs in O(N). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public void CopyTo(T[] array, int arrayIndex) + { + CopyTo(0, array, arrayIndex, this.Length); + } + + /// + /// Copies the a part of the rope into the specified array. + /// Runs in O(lg N + M). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + VerifyRange(index, count); + VerifyArrayWithRange(array, arrayIndex, count); + this.root.CopyTo(index, array, arrayIndex, count); + } + + /// + /// Removes the first occurance of an item from the rope. + /// Runs in O(N). + /// + public bool Remove(T item) + { + int index = IndexOf(item); + if (index >= 0) { + RemoveAt(index); + return true; + } + return false; + } + + /// + /// Retrieves an enumerator to iterate through the rope. + /// The enumerator will reflect the state of the rope from the GetEnumerator() call, further modifications + /// to the rope will not be visible to the enumerator. + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public IEnumerator GetEnumerator() + { + this.root.Publish(); + return Enumerate(root); + } + + /// + /// Creates an array and copies the contents of the rope into it. + /// Runs in O(N). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public T[] ToArray() + { + T[] arr = new T[this.Length]; + CopyTo(arr, 0); + return arr; + } + + /// + /// Creates an array and copies the contents of the rope into it. + /// Runs in O(N). + /// + /// + /// This method counts as a read access and may be called concurrently to other read accesses. + /// + public T[] ToArray(int startIndex, int count) + { + VerifyRange(startIndex, count); + T[] arr = new T[count]; + CopyTo(startIndex, arr, 0, count); + return arr; + } + + static IEnumerator Enumerate(RopeNode node) + { + Stack> stack = new Stack>(); + while (node != null) { + // go to leftmost node, pushing the right parts that we'll have to visit later + while (node.contents == null) { + if (node.height == 0) { + // go down into function nodes + node = node.GetContentNode(); + continue; + } + Debug.Assert(node.right != null); + stack.Push(node.right); + node = node.left; + } + // yield contents of leaf node + for (int i = 0; i < node.length; i++) { + yield return node.contents[i]; + } + // go up to the next node not visited yet + if (stack.Count > 0) + node = stack.Pop(); + else + node = null; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeNode.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeNode.cs new file mode 100644 index 000000000..87f060c1b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeNode.cs @@ -0,0 +1,605 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +using System.Text; + +namespace Tango.Scripting.Editors.Utils +{ + // Class used to represent a node in the tree. + // There are three types of nodes: + // Concat nodes: height>0, left!=null, right!=null, contents==null + // Leaf nodes: height==0, left==null, right==null, contents!=null + // Function nodes: height==0, left==null, right==null, contents==null, are of type FunctionNode + + [Serializable] + class RopeNode + { + internal const int NodeSize = 256; + + internal static readonly RopeNode emptyRopeNode = new RopeNode { isShared = true, contents = new T[RopeNode.NodeSize] }; + + // Fields for pointers to sub-nodes. Only non-null for concat nodes (height>=1) + internal RopeNode left, right; + internal volatile bool isShared; // specifies whether this node is shared between multiple ropes + // the total length of all text in this subtree + internal int length; + // the height of this subtree: 0 for leaf nodes; 1+max(left.height,right.height) for concat nodes + internal byte height; + + // The character data. Only non-null for leaf nodes (height=0) that aren't function nodes. + internal T[] contents; + + internal int Balance { + get { return right.height - left.height; } + } + + [Conditional("DATACONSISTENCYTEST")] + internal void CheckInvariants() + { + if (height == 0) { + Debug.Assert(left == null && right == null); + if (contents == null) { + Debug.Assert(this is FunctionNode); + Debug.Assert(length > 0); + Debug.Assert(isShared); + } else { + Debug.Assert(contents != null && contents.Length == NodeSize); + Debug.Assert(length >= 0 && length <= NodeSize); + } + } else { + Debug.Assert(left != null && right != null); + Debug.Assert(contents == null); + Debug.Assert(length == left.length + right.length); + Debug.Assert(height == 1 + Math.Max(left.height, right.height)); + Debug.Assert(Math.Abs(this.Balance) <= 1); + + // this is an additional invariant that forces the tree to combine small leafs to prevent excessive memory usage: + Debug.Assert(length > NodeSize); + // note that this invariant ensures that all nodes except for the empty rope's single node have at least length 1 + + if (isShared) + Debug.Assert(left.isShared && right.isShared); + left.CheckInvariants(); + right.CheckInvariants(); + } + } + + internal RopeNode Clone() + { + if (height == 0) { + // If a function node needs cloning, we'll evaluate it. + if (contents == null) + return GetContentNode().Clone(); + T[] newContents = new T[NodeSize]; + contents.CopyTo(newContents, 0); + return new RopeNode { + length = this.length, + contents = newContents + }; + } else { + return new RopeNode { + left = this.left, + right = this.right, + length = this.length, + height = this.height + }; + } + } + + internal RopeNode CloneIfShared() + { + if (isShared) + return Clone(); + else + return this; + } + + internal void Publish() + { + if (!isShared) { + if (left != null) + left.Publish(); + if (right != null) + right.Publish(); + // it's important that isShared=true is set at the end: + // Publish() must not return until the whole subtree is marked as shared, even when + // Publish() is called concurrently. + isShared = true; + } + } + + internal static RopeNode CreateFromArray(T[] arr, int index, int length) + { + if (length == 0) { + return emptyRopeNode; + } + RopeNode node = CreateNodes(length); + return node.StoreElements(0, arr, index, length); + } + + internal static RopeNode CreateNodes(int totalLength) + { + int leafCount = (totalLength + NodeSize - 1) / NodeSize; + return CreateNodes(leafCount, totalLength); + } + + static RopeNode CreateNodes(int leafCount, int totalLength) + { + Debug.Assert(leafCount > 0); + Debug.Assert(totalLength > 0); + RopeNode result = new RopeNode(); + result.length = totalLength; + if (leafCount == 1) { + result.contents = new T[NodeSize]; + } else { + int rightSide = leafCount / 2; + int leftSide = leafCount - rightSide; + int leftLength = leftSide * NodeSize; + result.left = CreateNodes(leftSide, leftLength); + result.right = CreateNodes(rightSide, totalLength - leftLength); + result.height = (byte)(1 + Math.Max(result.left.height, result.right.height)); + } + return result; + } + + /// + /// Balances this node and recomputes the 'height' field. + /// This method assumes that the children of this node are already balanced and have an up-to-date 'height' value. + /// + internal void Rebalance() + { + // Rebalance() shouldn't be called on shared nodes - it's only called after modifications! + Debug.Assert(!isShared); + // leaf nodes are always balanced (we don't use 'height' to detect leaf nodes here + // because Balance is supposed to recompute the height). + if (left == null) + return; + + // ensure we didn't miss a MergeIfPossible step + Debug.Assert(this.length > NodeSize); + + // We need to loop until it's balanced. Rotations might cause two small leaves to combine to a larger one, + // which changes the height and might mean we need additional balancing steps. + while (Math.Abs(this.Balance) > 1) { + // AVL balancing + // note: because we don't care about the identity of concat nodes, this works a little different than usual + // tree rotations: in our implementation, the "this" node will stay at the top, only its children are rearranged + if (this.Balance > 1) { + if (right.Balance < 0) { + right = right.CloneIfShared(); + right.RotateRight(); + } + this.RotateLeft(); + // If 'this' was unbalanced by more than 2, we've shifted some of the inbalance to the left node; so rebalance that. + this.left.Rebalance(); + } else if (this.Balance < -1) { + if (left.Balance > 0) { + left = left.CloneIfShared(); + left.RotateLeft(); + } + this.RotateRight(); + // If 'this' was unbalanced by more than 2, we've shifted some of the inbalance to the right node; so rebalance that. + this.right.Rebalance(); + } + } + + Debug.Assert(Math.Abs(this.Balance) <= 1); + this.height = (byte)(1 + Math.Max(left.height, right.height)); + } + + void RotateLeft() + { + Debug.Assert(!isShared); + + /* Rotate tree to the left + * + * this this + * / \ / \ + * A right ===> left C + * / \ / \ + * B C A B + */ + RopeNode a = left; + RopeNode b = right.left; + RopeNode c = right.right; + // reuse right concat node, if possible + this.left = right.isShared ? new RopeNode() : right; + this.left.left = a; + this.left.right = b; + this.left.length = a.length + b.length; + this.left.height = (byte)(1 + Math.Max(a.height, b.height)); + this.right = c; + + this.left.MergeIfPossible(); + } + + void RotateRight() + { + Debug.Assert(!isShared); + + /* Rotate tree to the right + * + * this this + * / \ / \ + * left C ===> A right + * / \ / \ + * A B B C + */ + RopeNode a = left.left; + RopeNode b = left.right; + RopeNode c = right; + // reuse left concat node, if possible + this.right = left.isShared ? new RopeNode() : left; + this.right.left = b; + this.right.right = c; + this.right.length = b.length + c.length; + this.right.height = (byte)(1 + Math.Max(b.height, c.height)); + this.left = a; + + this.right.MergeIfPossible(); + } + + void MergeIfPossible() + { + Debug.Assert(!isShared); + + if (this.length <= NodeSize) { + // Convert this concat node to leaf node. + // We know left and right cannot be concat nodes (they would have merged already), + // but they could be function nodes. + this.height = 0; + int lengthOnLeftSide = this.left.length; + if (this.left.isShared) { + this.contents = new T[NodeSize]; + left.CopyTo(0, this.contents, 0, lengthOnLeftSide); + } else { + // must be a leaf node: function nodes are always marked shared + Debug.Assert(this.left.contents != null); + // steal buffer from left side + this.contents = this.left.contents; + #if DEBUG + // In debug builds, explicitly mark left node as 'damaged' - but no one else should be using it + // because it's not shared. + this.left.contents = Empty.Array; + #endif + } + this.left = null; + right.CopyTo(0, this.contents, lengthOnLeftSide, this.right.length); + this.right = null; + } + } + + /// + /// Copies from the array to this node. + /// + internal RopeNode StoreElements(int index, T[] array, int arrayIndex, int count) + { + RopeNode result = this.CloneIfShared(); + // result cannot be function node after a call to Clone() + if (result.height == 0) { + // leaf node: + Array.Copy(array, arrayIndex, result.contents, index, count); + } else { + // concat node: + if (index + count <= result.left.length) { + result.left = result.left.StoreElements(index, array, arrayIndex, count); + } else if (index >= this.left.length) { + result.right = result.right.StoreElements(index - result.left.length, array, arrayIndex, count); + } else { + int amountInLeft = result.left.length - index; + result.left = result.left.StoreElements(index, array, arrayIndex, amountInLeft); + result.right = result.right.StoreElements(0, array, arrayIndex + amountInLeft, count - amountInLeft); + } + result.Rebalance(); // tree layout might have changed if function nodes were replaced with their content + } + return result; + } + + /// + /// Copies from this node to the array. + /// + internal void CopyTo(int index, T[] array, int arrayIndex, int count) + { + if (height == 0) { + if (this.contents == null) { + // function node + this.GetContentNode().CopyTo(index, array, arrayIndex, count); + } else { + // leaf node + Array.Copy(this.contents, index, array, arrayIndex, count); + } + } else { + // concat node + if (index + count <= this.left.length) { + this.left.CopyTo(index, array, arrayIndex, count); + } else if (index >= this.left.length) { + this.right.CopyTo(index - this.left.length, array, arrayIndex, count); + } else { + int amountInLeft = this.left.length - index; + this.left.CopyTo(index, array, arrayIndex, amountInLeft); + this.right.CopyTo(0, array, arrayIndex + amountInLeft, count - amountInLeft); + } + } + } + + internal RopeNode SetElement(int offset, T value) + { + RopeNode result = CloneIfShared(); + // result of CloneIfShared() is leaf or concat node + if (result.height == 0) { + result.contents[offset] = value; + } else { + if (offset < result.left.length) { + result.left = result.left.SetElement(offset, value); + } else { + result.right = result.right.SetElement(offset - result.left.length, value); + } + result.Rebalance(); // tree layout might have changed if function nodes were replaced with their content + } + return result; + } + + internal static RopeNode Concat(RopeNode left, RopeNode right) + { + if (left.length == 0) + return right; + if (right.length == 0) + return left; + + if (left.length + right.length <= NodeSize) { + left = left.CloneIfShared(); + // left is guaranteed to be leaf node after cloning: + // - it cannot be function node (due to clone) + // - it cannot be concat node (too short) + right.CopyTo(0, left.contents, left.length, right.length); + left.length += right.length; + return left; + } else { + RopeNode concatNode = new RopeNode(); + concatNode.left = left; + concatNode.right = right; + concatNode.length = left.length + right.length; + concatNode.Rebalance(); + return concatNode; + } + } + + /// + /// Splits this leaf node at offset and returns a new node with the part of the text after offset. + /// + RopeNode SplitAfter(int offset) + { + Debug.Assert(!isShared && height == 0 && contents != null); + RopeNode newPart = new RopeNode(); + newPart.contents = new T[NodeSize]; + newPart.length = this.length - offset; + Array.Copy(this.contents, offset, newPart.contents, 0, newPart.length); + this.length = offset; + return newPart; + } + + internal RopeNode Insert(int offset, RopeNode newElements) + { + if (offset == 0) { + return Concat(newElements, this); + } else if (offset == this.length) { + return Concat(this, newElements); + } + + // first clone this node (converts function nodes to leaf or concat nodes) + RopeNode result = CloneIfShared(); + if (result.height == 0) { + // leaf node: we'll need to split this node + RopeNode left = result; + RopeNode right = left.SplitAfter(offset); + return Concat(Concat(left, newElements), right); + } else { + // concat node + if (offset < result.left.length) { + result.left = result.left.Insert(offset, newElements); + } else { + result.right = result.right.Insert(offset - result.left.length, newElements); + } + result.length += newElements.length; + result.Rebalance(); + return result; + } + } + + internal RopeNode Insert(int offset, T[] array, int arrayIndex, int count) + { + Debug.Assert(count > 0); + + if (this.length + count < RopeNode.NodeSize) { + RopeNode result = CloneIfShared(); + // result must be leaf node (Clone never returns function nodes, too short for concat node) + int lengthAfterOffset = result.length - offset; + T[] resultContents = result.contents; + for (int i = lengthAfterOffset; i >= 0; i--) { + resultContents[i + offset + count] = resultContents[i + offset]; + } + Array.Copy(array, arrayIndex, resultContents, offset, count); + result.length += count; + return result; + } else if (height == 0) { + // TODO: implement this more efficiently? + return Insert(offset, CreateFromArray(array, arrayIndex, count)); + } else { + // this is a concat node (both leafs and function nodes are handled by the case above) + RopeNode result = CloneIfShared(); + if (offset < result.left.length) { + result.left = result.left.Insert(offset, array, arrayIndex, count); + } else { + result.right = result.right.Insert(offset - result.left.length, array, arrayIndex, count); + } + result.length += count; + result.Rebalance(); + return result; + } + } + + internal RopeNode RemoveRange(int index, int count) + { + Debug.Assert(count > 0); + + // produce empty node when one node is deleted completely + if (index == 0 && count == this.length) + return emptyRopeNode; + + int endIndex = index + count; + RopeNode result = CloneIfShared(); // convert function node to concat/leaf + if (result.height == 0) { + int remainingAfterEnd = result.length - endIndex; + for (int i = 0; i < remainingAfterEnd; i++) { + result.contents[index + i] = result.contents[endIndex + i]; + } + result.length -= count; + } else { + if (endIndex <= result.left.length) { + // deletion is only within the left part + result.left = result.left.RemoveRange(index, count); + } else if (index >= result.left.length) { + // deletion is only within the right part + result.right = result.right.RemoveRange(index - result.left.length, count); + } else { + // deletion overlaps both parts + int deletionAmountOnLeftSide = result.left.length - index; + result.left = result.left.RemoveRange(index, deletionAmountOnLeftSide); + result.right = result.right.RemoveRange(0, count - deletionAmountOnLeftSide); + } + // The deletion might have introduced empty nodes. Those must be removed. + if (result.left.length == 0) + return result.right; + if (result.right.length == 0) + return result.left; + + result.length -= count; + result.MergeIfPossible(); + result.Rebalance(); + } + return result; + } + + #region Debug Output + #if DEBUG + internal virtual void AppendTreeToString(StringBuilder b, int indent) + { + b.AppendLine(ToString()); + indent += 2; + if (left != null) { + b.Append(' ', indent); + b.Append("L: "); + left.AppendTreeToString(b, indent); + } + if (right != null) { + b.Append(' ', indent); + b.Append("R: "); + right.AppendTreeToString(b, indent); + } + } + + public override string ToString() + { + if (contents != null) { + char[] charContents = contents as char[]; + if (charContents != null) + return "[Leaf length=" + length + ", isShared=" + isShared + ", text=\"" + new string(charContents, 0, length) + "\"]"; + else + return "[Leaf length=" + length + ", isShared=" + isShared + "\"]"; + } else { + return "[Concat length=" + length + ", isShared=" + isShared + ", height=" + height + ", Balance=" + this.Balance + "]"; + } + } + + internal string GetTreeAsString() + { + StringBuilder b = new StringBuilder(); + AppendTreeToString(b, 0); + return b.ToString(); + } + #endif + #endregion + + /// + /// Gets the root node of the subtree from a lazily evaluated function node. + /// Such nodes are always marked as shared. + /// GetContentNode() will return either a Concat or Leaf node, never another FunctionNode. + /// + internal virtual RopeNode GetContentNode() + { + throw new InvalidOperationException("Called GetContentNode() on non-FunctionNode."); + } + } + + sealed class FunctionNode : RopeNode + { + Func> initializer; + RopeNode cachedResults; + + public FunctionNode(int length, Func> initializer) + { + Debug.Assert(length > 0); + Debug.Assert(initializer != null); + + this.length = length; + this.initializer = initializer; + // Function nodes are immediately shared, but cannot be cloned. + // This ensures we evaluate every initializer only once. + this.isShared = true; + } + + internal override RopeNode GetContentNode() + { + lock (this) { + if (this.cachedResults == null) { + if (this.initializer == null) + throw new InvalidOperationException("Trying to load this node recursively; or: a previous call to a rope initializer failed."); + Func> initializerCopy = this.initializer; + this.initializer = null; + Rope resultRope = initializerCopy(); + if (resultRope == null) + throw new InvalidOperationException("Rope initializer returned null."); + RopeNode resultNode = resultRope.root; + resultNode.Publish(); // result is shared between returned rope and the rope containing this function node + if (resultNode.length != this.length) + throw new InvalidOperationException("Rope initializer returned rope with incorrect length."); + if (resultNode.height == 0 && resultNode.contents == null) { + // ResultNode is another function node. + // We want to guarantee that GetContentNode() never returns function nodes, so we have to + // go down further in the tree. + this.cachedResults = resultNode.GetContentNode(); + } else { + this.cachedResults = resultNode; + } + } + return this.cachedResults; + } + } + + #if DEBUG + internal override void AppendTreeToString(StringBuilder b, int indent) + { + RopeNode resultNode; + lock (this) { + b.AppendLine(ToString()); + resultNode = cachedResults; + } + indent += 2; + if (resultNode != null) { + b.Append(' ', indent); + b.Append("C: "); + resultNode.AppendTreeToString(b, indent); + } + } + + public override string ToString() + { + return "[FunctionNode length=" + length + " initializerRan=" + (initializer == null) + "]"; + } + #endif + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeTextReader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeTextReader.cs new file mode 100644 index 000000000..43c8fc57b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/RopeTextReader.cs @@ -0,0 +1,105 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// TextReader implementation that reads text from a rope. + /// + public sealed class RopeTextReader : TextReader + { + Stack> stack = new Stack>(); + RopeNode currentNode; + int indexInsideNode; + + /// + /// Creates a new RopeTextReader. + /// Internally, this method creates a Clone of the rope; so the text reader will always read through the old + /// version of the rope if it is modified. + /// + public RopeTextReader(Rope rope) + { + if (rope == null) + throw new ArgumentNullException("rope"); + + // We force the user to iterate through a clone of the rope to keep the API contract of RopeTextReader simple + // (what happens when a rope is modified while iterating through it?) + rope.root.Publish(); + + // special case for the empty rope: + // leave currentNode initialized to null (RopeTextReader doesn't support empty nodes) + if (rope.Length != 0) { + currentNode = rope.root; + GoToLeftMostLeaf(); + } + } + + void GoToLeftMostLeaf() + { + while (currentNode.contents == null) { + if (currentNode.height == 0) { + // this is a function node - move to its contained rope + currentNode = currentNode.GetContentNode(); + continue; + } + Debug.Assert(currentNode.right != null); + stack.Push(currentNode.right); + currentNode = currentNode.left; + } + Debug.Assert(currentNode.height == 0); + } + + /// + public override int Peek() + { + if (currentNode == null) + return -1; + return currentNode.contents[indexInsideNode]; + } + + /// + public override int Read() + { + if (currentNode == null) + return -1; + char result = currentNode.contents[indexInsideNode++]; + if (indexInsideNode >= currentNode.length) + GoToNextNode(); + return result; + } + + void GoToNextNode() + { + if (stack.Count == 0) { + currentNode = null; + } else { + indexInsideNode = 0; + currentNode = stack.Pop(); + GoToLeftMostLeaf(); + } + } + + /// + public override int Read(char[] buffer, int index, int count) + { + if (currentNode == null) + return 0; + int amountInCurrentNode = currentNode.length - indexInsideNode; + if (count < amountInCurrentNode) { + Array.Copy(currentNode.contents, indexInsideNode, buffer, index, count); + indexInsideNode += count; + return count; + } else { + // read to end of current node + Array.Copy(currentNode.contents, indexInsideNode, buffer, index, amountInCurrentNode); + GoToNextNode(); + return amountInCurrentNode; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/StringSegment.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/StringSegment.cs new file mode 100644 index 000000000..f2fad60cf --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/StringSegment.cs @@ -0,0 +1,107 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Represents a string with a segment. + /// Similar to System.ArraySegment<T>, but for strings instead of arrays. + /// + public struct StringSegment : IEquatable + { + readonly string text; + readonly int offset; + readonly int count; + + /// + /// Creates a new StringSegment. + /// + public StringSegment(string text, int offset, int count) + { + if (text == null) + throw new ArgumentNullException("text"); + if (offset < 0 || offset > text.Length) + throw new ArgumentOutOfRangeException("offset"); + if (offset + count > text.Length) + throw new ArgumentOutOfRangeException("count"); + this.text = text; + this.offset = offset; + this.count = count; + } + + /// + /// Creates a new StringSegment. + /// + public StringSegment(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + this.text = text; + this.offset = 0; + this.count = text.Length; + } + + /// + /// Gets the string used for this segment. + /// + public string Text { + get { return text; } + } + + /// + /// Gets the start offset of the segment with the text. + /// + public int Offset { + get { return offset; } + } + + /// + /// Gets the length of the segment. + /// + public int Count { + get { return count; } + } + + #region Equals and GetHashCode implementation + /// + public override bool Equals(object obj) + { + if (obj is StringSegment) + return Equals((StringSegment)obj); // use Equals method below + else + return false; + } + + /// + public bool Equals(StringSegment other) + { + // add comparisions for all members here + return object.ReferenceEquals(this.text, other.text) && offset == other.offset && count == other.count; + } + + /// + public override int GetHashCode() + { + return text.GetHashCode() ^ offset ^ count; + } + + /// + /// Equality operator. + /// + public static bool operator ==(StringSegment left, StringSegment right) + { + return left.Equals(right); + } + + /// + /// Inequality operator. + /// + public static bool operator !=(StringSegment left, StringSegment right) + { + return !left.Equals(right); + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/TextFormatterFactory.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/TextFormatterFactory.cs new file mode 100644 index 000000000..dd6fe8b9e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/TextFormatterFactory.cs @@ -0,0 +1,71 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Creates TextFormatter instances that with the correct TextFormattingMode, if running on .NET 4.0. + /// + static class TextFormatterFactory + { + /// + /// Creates a using the formatting mode used by the specified owner object. + /// + public static TextFormatter Create(DependencyObject owner) + { + if (owner == null) + throw new ArgumentNullException("owner"); + return TextFormatter.Create(TextOptions.GetTextFormattingMode(owner)); + } + + /// + /// Returns whether the specified dependency property affects the text formatter creation. + /// Controls should re-create their text formatter for such property changes. + /// + public static bool PropertyChangeAffectsTextFormatter(DependencyProperty dp) + { + return dp == TextOptions.TextFormattingModeProperty; + } + + /// + /// Creates formatted text. + /// + /// The owner element. The text formatter setting are read from this element. + /// The text. + /// The typeface to use. If this parameter is null, the typeface of the will be used. + /// The font size. If this parameter is null, the font size of the will be used. + /// The foreground color. If this parameter is null, the foreground of the will be used. + /// A FormattedText object using the specified settings. + public static FormattedText CreateFormattedText(FrameworkElement element, string text, Typeface typeface, double? emSize, Brush foreground) + { + if (element == null) + throw new ArgumentNullException("element"); + if (text == null) + throw new ArgumentNullException("text"); + if (typeface == null) + typeface = element.CreateTypeface(); + if (emSize == null) + emSize = TextBlock.GetFontSize(element); + if (foreground == null) + foreground = TextBlock.GetForeground(element); + return new FormattedText( + text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + typeface, + emSize.Value, + foreground, + null, + TextOptions.GetTextFormattingMode(element) + ); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ThrowUtil.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ThrowUtil.cs new file mode 100644 index 000000000..69dbb5ad0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/ThrowUtil.cs @@ -0,0 +1,56 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Globalization; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Contains exception-throwing helper methods. + /// + static class ThrowUtil + { + /// + /// Throws an ArgumentNullException if is null; otherwise + /// returns val. + /// + /// + /// Use this method to throw an ArgumentNullException when using parameters for base + /// constructor calls. + /// + /// public VisualLineText(string text) : base(ThrowUtil.CheckNotNull(text, "text").Length) + /// + /// + public static T CheckNotNull(T val, string parameterName) where T : class + { + if (val == null) + throw new ArgumentNullException(parameterName); + return val; + } + + public static int CheckNotNegative(int val, string parameterName) + { + if (val < 0) + throw new ArgumentOutOfRangeException(parameterName, val, "value must not be negative"); + return val; + } + + public static int CheckInRangeInclusive(int val, string parameterName, int lower, int upper) + { + if (val < lower || val > upper) + throw new ArgumentOutOfRangeException(parameterName, val, "Expected: " + lower.ToString(CultureInfo.InvariantCulture) + " <= " + parameterName + " <= " + upper.ToString(CultureInfo.InvariantCulture)); + return val; + } + + public static InvalidOperationException NoDocumentAssigned() + { + return new InvalidOperationException("Document is null"); + } + + public static InvalidOperationException NoValidCaretPosition() + { + return new InvalidOperationException("Could not find a valid caret position in the line"); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/WeakEventManagerBase.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/WeakEventManagerBase.cs new file mode 100644 index 000000000..87e4de515 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/WeakEventManagerBase.cs @@ -0,0 +1,86 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// WeakEventManager with AddListener/RemoveListener and CurrentManager implementation. + /// Helps implementing the WeakEventManager pattern with less code. + /// + public abstract class WeakEventManagerBase : WeakEventManager + where TManager : WeakEventManagerBase, new() + where TEventSource : class + { + /// + /// Creates a new WeakEventManagerBase instance. + /// + protected WeakEventManagerBase() + { + Debug.Assert(GetType() == typeof(TManager)); + } + + /// + /// Adds a weak event listener. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static void AddListener(TEventSource source, IWeakEventListener listener) + { + CurrentManager.ProtectedAddListener(source, listener); + } + + /// + /// Removes a weak event listener. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static void RemoveListener(TEventSource source, IWeakEventListener listener) + { + CurrentManager.ProtectedRemoveListener(source, listener); + } + + /// + protected sealed override void StartListening(object source) + { + if (source == null) + throw new ArgumentNullException("source"); + StartListening((TEventSource)source); + } + + /// + protected sealed override void StopListening(object source) + { + if (source == null) + throw new ArgumentNullException("source"); + StopListening((TEventSource)source); + } + + /// + /// Attaches the event handler. + /// + protected abstract void StartListening(TEventSource source); + + /// + /// Detaches the event handler. + /// + protected abstract void StopListening(TEventSource source); + + /// + /// Gets the current manager. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + protected static TManager CurrentManager { + get { + Type managerType = typeof(TManager); + TManager manager = (TManager)GetCurrentManager(managerType); + if (manager == null) { + manager = new TManager(); + SetCurrentManager(managerType, manager); + } + return manager; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Win32.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Win32.cs new file mode 100644 index 000000000..421c35c5a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/Win32.cs @@ -0,0 +1,85 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; + +namespace Tango.Scripting.Editors.Utils +{ + /// + /// Wrapper around Win32 functions. + /// + static class Win32 + { + /// + /// Gets the caret blink time. + /// + public static TimeSpan CaretBlinkTime { + get { return TimeSpan.FromMilliseconds(SafeNativeMethods.GetCaretBlinkTime()); } + } + + /// + /// Creates an invisible Win32 caret for the specified Visual with the specified size (coordinates local to the owner visual). + /// + public static bool CreateCaret(Visual owner, Size size) + { + if (owner == null) + throw new ArgumentNullException("owner"); + HwndSource source = PresentationSource.FromVisual(owner) as HwndSource; + if (source != null) { + Vector r = owner.PointToScreen(new Point(size.Width, size.Height)) - owner.PointToScreen(new Point(0, 0)); + return SafeNativeMethods.CreateCaret(source.Handle, IntPtr.Zero, (int)Math.Ceiling(r.X), (int)Math.Ceiling(r.Y)); + } else { + return false; + } + } + + /// + /// Sets the position of the caret previously created using . position is relative to the owner visual. + /// + public static bool SetCaretPosition(Visual owner, Point position) + { + if (owner == null) + throw new ArgumentNullException("owner"); + HwndSource source = PresentationSource.FromVisual(owner) as HwndSource; + if (source != null) { + Point pointOnRootVisual = owner.TransformToAncestor(source.RootVisual).Transform(position); + Point pointOnHwnd = pointOnRootVisual.TransformToDevice(source.RootVisual); + return SafeNativeMethods.SetCaretPos((int)pointOnHwnd.X, (int)pointOnHwnd.Y); + } else { + return false; + } + } + + /// + /// Destroys the caret previously created using . + /// + public static bool DestroyCaret() + { + return SafeNativeMethods.DestroyCaret(); + } + + [SuppressUnmanagedCodeSecurity] + static class SafeNativeMethods + { + [DllImport("user32.dll")] + public static extern int GetCaretBlinkTime(); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetCaretPos(int x, int y); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DestroyCaret(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs new file mode 100644 index 000000000..b8b726ef9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs @@ -0,0 +1,129 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Name-value pair in a tag + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public class AXmlAttribute: AXmlObject + { + /// Name with namespace prefix - exactly as in source file + public string Name { get; internal set; } + /// Equals sign and surrounding whitespace + public string EqualsSign { get; internal set; } + /// The raw value - exactly as in source file (*probably* quoted and escaped) + public string QuotedValue { get; internal set; } + /// Unquoted and dereferenced value of the attribute + public string Value { get; internal set; } + + internal override void DebugCheckConsistency(bool checkParentPointers) + { + DebugAssert(Name != null, "Null Name"); + DebugAssert(EqualsSign != null, "Null EqualsSign"); + DebugAssert(QuotedValue != null, "Null QuotedValue"); + DebugAssert(Value != null, "Null Value"); + base.DebugCheckConsistency(checkParentPointers); + } + + #region Helpper methods + + /// The element containing this attribute + /// Null if orphaned + public AXmlElement ParentElement { + get { + AXmlTag tag = this.Parent as AXmlTag; + if (tag != null) { + return tag.Parent as AXmlElement; + } + return null; + } + } + + /// The part of name before ":" + /// Empty string if not found + public string Prefix { + get { + return GetNamespacePrefix(this.Name); + } + } + + /// The part of name after ":" + /// Whole name if ":" not found + public string LocalName { + get { + return GetLocalName(this.Name); + } + } + + /// + /// Resolved namespace of the name. Empty string if not found + /// From the specification: "The namespace name for an unprefixed attribute name always has no value." + /// + public string Namespace { + get { + if (string.IsNullOrEmpty(this.Prefix)) return NoNamespace; + + AXmlElement elem = this.ParentElement; + if (elem != null) { + return elem.ResolvePrefix(this.Prefix); + } + return NoNamespace; // Orphaned attribute + } + } + + /// Attribute is declaring namespace ("xmlns" or "xmlns:*") + public bool IsNamespaceDeclaration { + get { + return this.Name == "xmlns" || this.Prefix == "xmlns"; + } + } + + #endregion + + /// + public override void AcceptVisitor(IAXmlVisitor visitor) + { + visitor.VisitAttribute(this); + } + + /// + internal override bool UpdateDataFrom(AXmlObject source) + { + if (!base.UpdateDataFrom(source)) return false; + AXmlAttribute src = (AXmlAttribute)source; + if (this.Name != src.Name || + this.EqualsSign != src.EqualsSign || + this.QuotedValue != src.QuotedValue || + this.Value != src.Value) + { + OnChanging(); + this.Name = src.Name; + this.EqualsSign = src.EqualsSign; + this.QuotedValue = src.QuotedValue; + this.Value = src.Value; + OnChanged(); + return true; + } else { + return false; + } + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}']", base.ToString(), this.Name, this.EqualsSign, this.Value); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs new file mode 100644 index 000000000..95fa83cd0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs @@ -0,0 +1,119 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Specailized attribute collection with attribute name caching + /// + public class AXmlAttributeCollection: FilteredCollection> + { + /// Empty unbound collection + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", + Justification = "InsertItem prevents modifying the Empty collection")] + public static readonly AXmlAttributeCollection Empty = new AXmlAttributeCollection(); + + /// Create unbound collection + protected AXmlAttributeCollection() {} + + /// Wrap the given collection. Non-attributes are filtered + public AXmlAttributeCollection(AXmlObjectCollection source): base(source) {} + + /// Wrap the given collection. Non-attributes are filtered. Items not matching the condition are filtered. + public AXmlAttributeCollection(AXmlObjectCollection source, Predicate condition): base(source, condition) {} + + Dictionary> hashtable = new Dictionary>(); + + void AddToHashtable(AXmlAttribute attr) + { + string localName = attr.LocalName; + if (!hashtable.ContainsKey(localName)) { + hashtable[localName] = new List(1); + } + hashtable[localName].Add(attr); + } + + void RemoveFromHashtable(AXmlAttribute attr) + { + string localName = attr.LocalName; + hashtable[localName].Remove(attr); + } + + static List NoAttributes = new List(); + + /// + /// Get all attributes with given local name. + /// Hash table is used for lookup so this is cheap. + /// + public IEnumerable GetByLocalName(string localName) + { + if (hashtable.ContainsKey(localName)) { + return hashtable[localName]; + } else { + return NoAttributes; + } + } + + /// + protected override void ClearItems() + { + foreach(AXmlAttribute item in this) { + RemoveFromHashtable(item); + item.Changing -= item_Changing; + item.Changed -= item_Changed; + } + base.ClearItems(); + } + + /// + protected override void InsertItem(int index, AXmlAttribute item) + { + // prevent insertions into the static 'Empty' instance + if (this == Empty) + throw new NotSupportedException("Cannot insert into AXmlAttributeCollection.Empty"); + + AddToHashtable(item); + item.Changing += item_Changing; + item.Changed += item_Changed; + base.InsertItem(index, item); + } + + /// + protected override void RemoveItem(int index) + { + RemoveFromHashtable(this[index]); + this[index].Changing -= item_Changing; + this[index].Changed -= item_Changed; + base.RemoveItem(index); + } + + /// + protected override void SetItem(int index, AXmlAttribute item) + { + RemoveFromHashtable(this[index]); + this[index].Changing -= item_Changing; + this[index].Changed -= item_Changed; + + AddToHashtable(item); + item.Changing += item_Changing; + item.Changed += item_Changed; + base.SetItem(index, item); + } + + // Every item in the collection should be registered to these handlers + // so that we can handle renames + + void item_Changing(object sender, AXmlObjectEventArgs e) + { + RemoveFromHashtable((AXmlAttribute)e.Object); + } + + void item_Changed(object sender, AXmlObjectEventArgs e) + { + AddToHashtable((AXmlAttribute)e.Object); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs new file mode 100644 index 000000000..3cc716de5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs @@ -0,0 +1,282 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Abstact base class for all types that can contain child nodes + /// + public abstract class AXmlContainer: AXmlObject + { + /// + /// Children of the node. It is read-only. + /// Note that is has CollectionChanged event. + /// + public AXmlObjectCollection Children { get; private set; } + + /// Create new container + protected AXmlContainer() + { + this.Children = new AXmlObjectCollection(); + } + + #region Helpper methods + + ObservableCollection elements; + + /// Gets direcly nested elements (non-recursive) + public ObservableCollection Elements { + get { + if (elements == null) { + elements = new FilteredCollection>(this.Children); + } + return elements; + } + } + + internal AXmlObject FirstChild { + get { + return this.Children[0]; + } + } + + internal AXmlObject LastChild { + get { + return this.Children[this.Children.Count - 1]; + } + } + + #endregion + + /// + public override IEnumerable GetSelfAndAllChildren() + { + return (new AXmlObject[] { this }).Flatten( + delegate(AXmlObject i) { + AXmlContainer container = i as AXmlContainer; + if (container != null) + return container.Children; + else + return null; + } + ); + } + + /// + /// Gets a child fully containg the given offset. + /// Goes recursively down the tree. + /// Specail case if at the end of attribute or text + /// + public AXmlObject GetChildAtOffset(int offset) + { + foreach(AXmlObject child in this.Children) { + if ((child is AXmlAttribute || child is AXmlText) && offset == child.EndOffset) return child; + if (child.StartOffset < offset && offset < child.EndOffset) { + AXmlContainer container = child as AXmlContainer; + if (container != null) { + return container.GetChildAtOffset(offset); + } else { + return child; + } + } + } + return this; // No childs at offset + } + + // Only these four methods should be used to modify the collection + + /// To be used exlucively by the parser + internal void AddChild(AXmlObject item) + { + // Childs can be only added to newly parsed items + Assert(this.Parent == null, "I have to be new"); + Assert(item.IsCached, "Added item must be in cache"); + // Do not set parent pointer + this.Children.InsertItemAt(this.Children.Count, item); + } + + /// To be used exlucively by the parser + internal void AddChildren(IEnumerable items) + { + // Childs can be only added to newly parsed items + Assert(this.Parent == null, "I have to be new"); + // Do not set parent pointer + this.Children.InsertItemsAt(this.Children.Count, items.ToList()); + } + + /// + /// To be used exclusively by the children update algorithm. + /// Insert child and keep links consistent. + /// + void InsertChild(int index, AXmlObject item) + { + AXmlParser.Log("Inserting {0} at index {1}", item, index); + + Assert(this.Document != null, "Can not insert to dangling object"); + Assert(item.Parent != this, "Can not own item twice"); + + SetParentPointersInTree(item); + + this.Children.InsertItemAt(index, item); + + this.Document.OnObjectInserted(index, item); + } + + /// Recursively fix all parent pointer in a tree + /// + /// Cache constraint: + /// If cached item has parent set, then the whole subtree must be consistent and document set + /// + void SetParentPointersInTree(AXmlObject item) + { + // All items come from the parser cache + + if (item.Parent == null) { + // Dangling object - either a new parser object or removed tree (still cached) + item.Parent = this; + item.Document = this.Document; + AXmlContainer container = item as AXmlContainer; + if (container != null) { + foreach(AXmlObject child in container.Children) { + container.SetParentPointersInTree(child); + } + } + } else if (item.Parent == this) { + // If node is attached and then deattached, it will have null parent pointer + // but valid subtree - so its children will alredy have correct parent pointer + // like in this case + // item.DebugCheckConsistency(false); + // Rest of the tree is consistent - do not recurse + } else { + // From cache & parent set => consitent subtree + // item.DebugCheckConsistency(false); + // The parent (or any futher parents) can not be part of parsed document + // becuase otherwise this item would be included twice => safe to change parents + // Maintain cache constraint by setting parents to null + foreach(AXmlObject ancest in item.GetAncestors().ToList()) { + ancest.Parent = null; + } + item.Parent = this; + // Rest of the tree is consistent - do not recurse + } + } + + /// + /// To be used exclusively by the children update algorithm. + /// Remove child, set parent to null and notify the document + /// + void RemoveChild(int index) + { + AXmlObject removed = this.Children[index]; + AXmlParser.Log("Removing {0} at index {1}", removed, index); + + // Stop tracking if the object can not be used again + if (!removed.IsCached) + this.Document.Parser.TrackedSegments.RemoveParsedObject(removed); + + // Null parent pointer + Assert(removed.Parent == this, "Inconsistent child"); + removed.Parent = null; + + this.Children.RemoveItemAt(index); + + this.Document.OnObjectRemoved(index, removed); + } + + /// Verify that the subtree is consistent. Only in debug build. + /// Parent pointers might be null or pointing somewhere else in parse tree + internal override void DebugCheckConsistency(bool checkParentPointers) + { + base.DebugCheckConsistency(checkParentPointers); + AXmlObject prevChild = null; + int myStartOffset = this.StartOffset; + int myEndOffset = this.EndOffset; + foreach(AXmlObject child in this.Children) { + Assert(child.Length != 0, "Empty child"); + if (checkParentPointers) { + Assert(child.Parent != null, "Null parent reference"); + Assert(child.Parent == this, "Inccorect parent reference"); + } + if (this.Document != null) { + Assert(child.Document != null, "Child has null document"); + Assert(child.Document == this.Document, "Child is in different document"); + } + if (this.IsCached) + Assert(child.IsCached, "Child not in cache"); + Assert(myStartOffset <= child.StartOffset && child.EndOffset <= myEndOffset, "Child not within parent text range"); + if (prevChild != null) + Assert(prevChild.EndOffset <= child.StartOffset, "Overlaping childs"); + child.DebugCheckConsistency(checkParentPointers); + prevChild = child; + } + } + + /// + /// Note the the method is not called recuively. + /// Only the helper methods are recursive. + /// + internal void UpdateTreeFrom(AXmlContainer srcContainer) + { + this.StartOffset = srcContainer.StartOffset; // Force the update + this.UpdateDataFrom(srcContainer); + RemoveChildrenNotIn(srcContainer.Children); + InsertAndUpdateChildrenFrom(srcContainer.Children); + } + + void RemoveChildrenNotIn(IList srcList) + { + Dictionary srcChildren = srcList.ToDictionary(i => i.StartOffset); + for(int i = 0; i < this.Children.Count;) { + AXmlObject child = this.Children[i]; + AXmlObject srcChild; + + if (srcChildren.TryGetValue(child.StartOffset, out srcChild) && child.CanUpdateDataFrom(srcChild)) { + // Keep only one item with given offset (we might have several due to deletion) + srcChildren.Remove(child.StartOffset); + // If contaner that needs updating + AXmlContainer childAsContainer = child as AXmlContainer; + if (childAsContainer != null && child.LastUpdatedFrom != srcChild) + childAsContainer.RemoveChildrenNotIn(((AXmlContainer)srcChild).Children); + i++; + } else { + RemoveChild(i); + } + } + } + + void InsertAndUpdateChildrenFrom(IList srcList) + { + for(int i = 0; i < srcList.Count; i++) { + // End of our list? + if (i == this.Children.Count) { + InsertChild(i, srcList[i]); + continue; + } + AXmlObject child = this.Children[i]; + AXmlObject srcChild = srcList[i]; + + if (child.CanUpdateDataFrom(srcChild)) { // includes offset test + // Does it need updating? + if (child.LastUpdatedFrom != srcChild) { + child.UpdateDataFrom(srcChild); + AXmlContainer childAsContainer = child as AXmlContainer; + if (childAsContainer != null) + childAsContainer.InsertAndUpdateChildrenFrom(((AXmlContainer)srcChild).Children); + } + } else { + InsertChild(i, srcChild); + } + } + Assert(this.Children.Count == srcList.Count, "List lengths differ after update"); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs new file mode 100644 index 000000000..f96c50ca4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs @@ -0,0 +1,69 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// The root object of the XML document + /// + public class AXmlDocument: AXmlContainer + { + /// Parser that produced this document + internal AXmlParser Parser { get; set; } + + /// Occurs when object is added to any part of the document + public event EventHandler ObjectInserted; + /// Occurs when object is removed from any part of the document + public event EventHandler ObjectRemoved; + /// Occurs before local data of any object in the document changes + public event EventHandler ObjectChanging; + /// Occurs after local data of any object in the document changed + public event EventHandler ObjectChanged; + + internal void OnObjectInserted(int index, AXmlObject obj) + { + if (ObjectInserted != null) + ObjectInserted(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new AXmlObject[] { obj }.ToList(), index)); + } + + internal void OnObjectRemoved(int index, AXmlObject obj) + { + if (ObjectRemoved != null) + ObjectRemoved(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new AXmlObject[] { obj }.ToList(), index)); + } + + internal void OnObjectChanging(AXmlObject obj) + { + if (ObjectChanging != null) + ObjectChanging(this, new AXmlObjectEventArgs() { Object = obj } ); + } + + internal void OnObjectChanged(AXmlObject obj) + { + if (ObjectChanged != null) + ObjectChanged(this, new AXmlObjectEventArgs() { Object = obj } ); + } + + /// + public override void AcceptVisitor(IAXmlVisitor visitor) + { + visitor.VisitDocument(this); + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0} Chld:{1}]", base.ToString(), this.Children.Count); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs new file mode 100644 index 000000000..080d538e8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs @@ -0,0 +1,226 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Logical grouping of other nodes together. + /// + public class AXmlElement: AXmlContainer + { + /// No tags are missing anywhere within this element (recursive) + public bool IsProperlyNested { get; set; } + /// True in wellformed XML + public bool HasStartOrEmptyTag { get; set; } + /// True in wellformed XML + public bool HasEndTag { get; set; } + + /// + internal override bool UpdateDataFrom(AXmlObject source) + { + if (!base.UpdateDataFrom(source)) return false; + AXmlElement src = (AXmlElement)source; + // Clear the cache for this - quite expensive + attributesAndElements = null; + if (this.IsProperlyNested != src.IsProperlyNested || + this.HasStartOrEmptyTag != src.HasStartOrEmptyTag || + this.HasEndTag != src.HasEndTag) + { + OnChanging(); + this.IsProperlyNested = src.IsProperlyNested; + this.HasStartOrEmptyTag = src.HasStartOrEmptyTag; + this.HasEndTag = src.HasEndTag; + OnChanged(); + return true; + } else { + return false; + } + } + + /// The start or empty-element tag if there is any + internal AXmlTag StartTag { + get { + Assert(HasStartOrEmptyTag, "Does not have a start tag"); + return (AXmlTag)this.Children[0]; + } + } + + /// The end tag if there is any + internal AXmlTag EndTag { + get { + Assert(HasEndTag, "Does not have an end tag"); + return (AXmlTag)this.Children[this.Children.Count - 1]; + } + } + + internal override void DebugCheckConsistency(bool checkParentPointers) + { + DebugAssert(Children.Count > 0, "No children"); + base.DebugCheckConsistency(checkParentPointers); + } + + #region Helpper methods + + /// Gets attributes of the element + /// + /// Warning: this is a cenvenience method to access the attributes of the start tag. + /// However, since the start tag might be moved/replaced, this property might return + /// different values over time. + /// + public AXmlAttributeCollection Attributes { + get { + if (this.HasStartOrEmptyTag) { + return this.StartTag.Attributes; + } else { + return AXmlAttributeCollection.Empty; + } + } + } + + ObservableCollection attributesAndElements; + + /// Gets both attributes and elements. Expensive, avoid use. + /// Warning: the collection will regenerate after each update + public ObservableCollection AttributesAndElements { + get { + if (attributesAndElements == null) { + if (this.HasStartOrEmptyTag) { + attributesAndElements = new MergedCollection> ( + // New wrapper with RawObject types + new FilteredCollection>(this.StartTag.Children, x => x is AXmlAttribute), + new FilteredCollection>(this.Children, x => x is AXmlElement) + ); + } else { + attributesAndElements = new FilteredCollection>(this.Children, x => x is AXmlElement); + } + } + return attributesAndElements; + } + } + + /// Name with namespace prefix - exactly as in source + public string Name { + get { + if (this.HasStartOrEmptyTag) { + return this.StartTag.Name; + } else { + return this.EndTag.Name; + } + } + } + + /// The part of name before ":" + /// Empty string if not found + public string Prefix { + get { + return GetNamespacePrefix(this.Name); + } + } + + /// The part of name after ":" + /// Empty string if not found + public string LocalName { + get { + return GetLocalName(this.Name); + } + } + + /// Resolved namespace of the name + /// Empty string if prefix is not found + public string Namespace { + get { + string prefix = this.Prefix; + if (string.IsNullOrEmpty(prefix)) { + return FindDefaultNamespace(); + } else { + return ResolvePrefix(prefix); + } + } + } + + /// Find the defualt namespace for this context + public string FindDefaultNamespace() + { + AXmlElement current = this; + while(current != null) { + string namesapce = current.GetAttributeValue(NoNamespace, "xmlns"); + if (namesapce != null) return namesapce; + current = current.Parent as AXmlElement; + } + return string.Empty; // No namesapce + } + + /// + /// Recursively resolve given prefix in this context. Prefix must have some value. + /// + /// Empty string if prefix is not found + public string ResolvePrefix(string prefix) + { + if (string.IsNullOrEmpty(prefix)) throw new ArgumentException("No prefix given", "prefix"); + + // Implicit namesapces + if (prefix == "xml") return XmlNamespace; + if (prefix == "xmlns") return XmlnsNamespace; + + AXmlElement current = this; + while(current != null) { + string namesapce = current.GetAttributeValue(XmlnsNamespace, prefix); + if (namesapce != null) return namesapce; + current = current.Parent as AXmlElement; + } + return NoNamespace; // Can not find prefix + } + + /// + /// Get unquoted value of attribute. + /// It looks in the no namespace (empty string). + /// + /// Null if not found + public string GetAttributeValue(string localName) + { + return GetAttributeValue(NoNamespace, localName); + } + + /// + /// Get unquoted value of attribute + /// + /// Namespace. Can be no namepace (empty string), which is the default for attributes. + /// Local name - text after ":" + /// Null if not found + public string GetAttributeValue(string @namespace, string localName) + { + @namespace = @namespace ?? string.Empty; + foreach(AXmlAttribute attr in this.Attributes.GetByLocalName(localName)) { + DebugAssert(attr.LocalName == localName, "Bad hashtable"); + if (attr.Namespace == @namespace) { + return attr.Value; + } + } + return null; + } + + #endregion + + /// + public override void AcceptVisitor(IAXmlVisitor visitor) + { + visitor.VisitElement(this); + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}' Attr:{2} Chld:{3} Nest:{4}]", base.ToString(), this.Name, this.HasStartOrEmptyTag ? this.StartTag.Children.Count : 0, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad"); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs new file mode 100644 index 000000000..4951a7c0b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs @@ -0,0 +1,266 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Abstact base class for all types + /// + public abstract class AXmlObject: TextSegment + { + /// Empty string. The namespace used if there is no "xmlns" specified + public static readonly string NoNamespace = string.Empty; + + /// Namespace for "xml:" prefix: "http://www.w3.org/XML/1998/namespace" + public static readonly string XmlNamespace = "http://www.w3.org/XML/1998/namespace"; + + /// Namesapce for "xmlns:" prefix: "http://www.w3.org/2000/xmlns/" + public static readonly string XmlnsNamespace = "http://www.w3.org/2000/xmlns/"; + + /// Parent node. + /// + /// New cached items start with null parent. + /// Cache constraint: + /// If cached item has parent set, then the whole subtree must be consistent + /// + public AXmlObject Parent { get; set; } + + /// + /// Gets the document that has owns this object. + /// Once set, it is not changed. Not even set to null. + /// + internal AXmlDocument Document { get; set; } + + /// Creates new object + protected AXmlObject() + { + this.LastUpdatedFrom = this; + } + + /// Occurs before the value of any local properties changes. Nested changes do not cause the event to occur + public event EventHandler Changing; + + /// Occurs after the value of any local properties changed. Nested changes do not cause the event to occur + public event EventHandler Changed; + + /// Raises Changing event + protected void OnChanging() + { + AXmlParser.Log("Changing {0}", this); + if (Changing != null) { + Changing(this, new AXmlObjectEventArgs() { Object = this } ); + } + AXmlDocument doc = this.Document; + if (doc != null) { + doc.OnObjectChanging(this); + } + // As a convenience, also rasie an event for the parent element + AXmlTag me = this as AXmlTag; + if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { + me.Parent.OnChanging(); + } + } + + /// Raises Changed event + protected void OnChanged() + { + AXmlParser.Log("Changed {0}", this); + if (Changed != null) { + Changed(this, new AXmlObjectEventArgs() { Object = this } ); + } + AXmlDocument doc = this.Document; + if (doc != null) { + doc.OnObjectChanged(this); + } + // As a convenience, also rasie an event for the parent element + AXmlTag me = this as AXmlTag; + if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { + me.Parent.OnChanged(); + } + } + + List syntaxErrors; + + /// + /// The error that occured in the context of this node (excluding nested nodes) + /// + public IEnumerable MySyntaxErrors { + get { + if (syntaxErrors == null) { + return new SyntaxError[] {}; + } else { + return syntaxErrors; + } + } + } + + /// + /// The error that occured in the context of this node and all nested nodes. + /// It has O(n) cost. + /// + public IEnumerable SyntaxErrors { + get { + return GetSelfAndAllChildren().SelectMany(obj => obj.MySyntaxErrors); + } + } + + internal void AddSyntaxError(SyntaxError error) + { + DebugAssert(error.Object == this, "Must own the error"); + if (this.syntaxErrors == null) this.syntaxErrors = new List(); + syntaxErrors.Add(error); + } + + /// Throws exception if condition is false + /// Present in release mode - use only for very cheap aserts + protected static void Assert(bool condition, string message) + { + if (!condition) { + throw new InternalException("Assertion failed: " + message); + } + } + + /// Throws exception if condition is false + [Conditional("DEBUG")] + protected static void DebugAssert(bool condition, string message) + { + if (!condition) { + throw new InternalException("Assertion failed: " + message); + } + } + + /// Recursively gets self and all nested nodes. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")] + public virtual IEnumerable GetSelfAndAllChildren() + { + return new AXmlObject[] { this }; + } + + /// Get all ancestors of this node + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")] + public IEnumerable GetAncestors() + { + AXmlObject curr = this.Parent; + while(curr != null) { + yield return curr; + curr = curr.Parent; + } + } + + /// Call appropriate visit method on the given visitor + public abstract void AcceptVisitor(IAXmlVisitor visitor); + + /// The parser tree object this object was updated from + /// Initialized to 'this' + internal AXmlObject LastUpdatedFrom { get; private set; } + + internal bool IsCached { get; set; } + + /// Is call to UpdateDataFrom is allowed? + internal bool CanUpdateDataFrom(AXmlObject source) + { + return + this.GetType() == source.GetType() && + this.StartOffset == source.StartOffset && + (this.LastUpdatedFrom == source || !this.IsCached); + } + + /// Copy all data from the 'source' to this object + /// Returns true if any updates were done + internal virtual bool UpdateDataFrom(AXmlObject source) + { + Assert(this.GetType() == source.GetType(), "Source has different type"); + DebugAssert(this.StartOffset == source.StartOffset, "Source has different StartOffset"); + + if (this.LastUpdatedFrom == source) { + DebugAssert(this.EndOffset == source.EndOffset, "Source has different EndOffset"); + return false; + } + + Assert(!this.IsCached, "Can not update cached item"); + Assert(source.IsCached, "Must update from cache"); + + this.LastUpdatedFrom = source; + this.StartOffset = source.StartOffset; + // In some cases we are just updating objects of that same + // type and position and hoping to be luckily right + this.EndOffset = source.EndOffset; + + // Do not bother comparing - assume changed if non-null + if (this.syntaxErrors != null || source.syntaxErrors != null) { + // May be called again in derived class - oh, well, does not matter + OnChanging(); + this.Document.Parser.TrackedSegments.RemoveSyntaxErrorsOf(this); + if (source.syntaxErrors == null) { + this.syntaxErrors = null; + } else { + this.syntaxErrors = new List(); + foreach(var error in source.MySyntaxErrors) { + // The object differs, so create our own copy + // The source still might need it in the future and we do not want to break it + this.AddSyntaxError(error.Clone(this)); + } + } + this.Document.Parser.TrackedSegments.AddSyntaxErrorsOf(this); + OnChanged(); + } + + return true; + } + + /// Verify that the item is consistent. Only in debug build. + [Conditional("DEBUG")] + internal virtual void DebugCheckConsistency(bool allowNullParent) + { + + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}({1}-{2})", this.GetType().Name.Remove(0, 4), this.StartOffset, this.EndOffset); + } + + #region Helpper methods + + /// The part of name before ":" + /// Empty string if not found + protected static string GetNamespacePrefix(string name) + { + if (string.IsNullOrEmpty(name)) return string.Empty; + int colonIndex = name.IndexOf(':'); + if (colonIndex != -1) { + return name.Substring(0, colonIndex); + } else { + return string.Empty; + } + } + + /// The part of name after ":" + /// Whole name if ":" not found + protected static string GetLocalName(string name) + { + if (string.IsNullOrEmpty(name)) return string.Empty; + int colonIndex = name.IndexOf(':'); + if (colonIndex != -1) { + return name.Remove(0, colonIndex + 1); + } else { + return name ?? string.Empty; + } + } + + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs new file mode 100644 index 000000000..3a2c7cc21 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs @@ -0,0 +1,90 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Collection that is publicly read-only and has support + /// for adding/removing multiple items at a time. + /// + public class AXmlObjectCollection: Collection, INotifyCollectionChanged + { + /// Occurs when the collection is changed + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// Raises event + // Do not inherit - it is not called if event is null + void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (CollectionChanged != null) { + CollectionChanged(this, e); + } + } + + /// + protected override void ClearItems() + { + throw new NotSupportedException(); + } + + /// + protected override void InsertItem(int index, T item) + { + throw new NotSupportedException(); + } + + /// + protected override void RemoveItem(int index) + { + throw new NotSupportedException(); + } + + /// + protected override void SetItem(int index, T item) + { + throw new NotSupportedException(); + } + + internal void InsertItemAt(int index, T item) + { + base.InsertItem(index, item); + if (CollectionChanged != null) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new T[] { item }.ToList(), index)); + } + + internal void RemoveItemAt(int index) + { + T removed = this[index]; + base.RemoveItem(index); + if (CollectionChanged != null) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new T[] { removed }.ToList(), index)); + } + + internal void InsertItemsAt(int index, IList items) + { + for(int i = 0; i < items.Count; i++) { + base.InsertItem(index + i, items[i]); + } + if (CollectionChanged != null) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)items, index)); + } + + internal void RemoveItemsAt(int index, int count) + { + List removed = new List(); + for(int i = 0; i < count; i++) { + removed.Add(this[index]); + base.RemoveItem(index); + } + if (CollectionChanged != null) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)removed, index)); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs new file mode 100644 index 000000000..27ea041e6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs @@ -0,0 +1,21 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// Holds event args for event caused by + public class AXmlObjectEventArgs: EventArgs + { + /// The object that caused the event + public AXmlObject Object { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs new file mode 100644 index 000000000..625ff59d9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs @@ -0,0 +1,201 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Creates object tree from XML document. + /// + /// + /// The created tree fully describes the document and thus the orginal XML file can be + /// exactly reproduced. + /// + /// Any further parses will reparse only the changed parts and the existing tree will + /// be updated with the changes. The user can add event handlers to be notified of + /// the changes. The parser tries to minimize the number of changes to the tree. + /// (for example, it will add a single child at the start of collection rather than + /// clearing the collection and adding new children) + /// + /// The object tree consists of following types: + /// RawObject - Abstact base class for all types + /// RawContainer - Abstact base class for all types that can contain child nodes + /// RawDocument - The root object of the XML document + /// RawElement - Logical grouping of other nodes together. The first child is always the start tag. + /// RawTag - Represents any markup starting with "<" and (hopefully) ending with ">" + /// RawAttribute - Name-value pair in a tag + /// RawText - Whitespace or character data + /// + /// For example, see the following XML and the produced object tree: + /// + /// + /// Make everything as simple as possible, but not simpler. + /// + /// + /// RawDocument + /// RawTag "" + /// RawText " My favourite quote " + /// RawElement + /// RawTag "<" "quote" ">" + /// RawText " " + /// RawAttribute 'author="Albert Einstein"' + /// RawText "\n Make everything as simple as possible, but not simpler.\n" + /// RawTag "" + /// ]]> + /// + /// The precise content of RawTag depends on what it represents: + /// " | "/>") + /// End tag: "" + /// P.instr.: "" + /// Comment: "" + /// CData: "" + /// DTD: "" (DOCTYPE or other DTD names) + /// UknownBang: "" + /// ]]> + /// + /// The type of tag can be identified by the opening backet. + /// There are helpper properties in the RawTag class to identify the type, exactly + /// one of the properties will be true. + /// + /// The closing bracket may be missing or may be different for mallformed XML. + /// + /// Note that there can always be multiple consequtive RawText nodes. + /// This is to ensure that idividual texts are not too long. + /// + /// XML Spec: http://www.w3.org/TR/xml/ + /// XML EBNF: http://www.jelks.nu/XML/xmlebnf.html + /// + /// Internals: + /// + /// "Try" methods can silently fail by returning false. + /// MoveTo methods do not move if they are already at the given target + /// If methods return some object, it must be no-empty. It is up to the caller to ensure + /// the context is appropriate for reading. + /// + /// + public class AXmlParser + { + AXmlDocument userDocument; + + internal TrackedSegmentCollection TrackedSegments { get; private set; } + + /// + /// Generate syntax error when seeing enity reference other then the build-in ones + /// + public bool UnknownEntityReferenceIsError { get; set; } + + /// Create new parser + public AXmlParser() + { + this.Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + ClearInternal(); + } + + /// Throws exception if condition is false + internal static void Assert(bool condition, string message) + { + if (!condition) { + throw new InternalException("Assertion failed: " + message); + } + } + + /// Throws exception if condition is false + [Conditional("DEBUG")] + internal static void DebugAssert(bool condition, string message) + { + if (!condition) { + throw new InternalException("Assertion failed: " + message); + } + } + + [Conditional("DEBUG")] + internal static void Log(string text, params object[] pars) + { + //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "XML: " + text, pars)); + } + + /// + /// Incrementaly parse the given text. + /// You have to hold the write lock. + /// + /// + /// The full XML text of the new document. + /// + /// + /// Changes since last parse. Null will cause full reparse. + /// + public AXmlDocument Parse(string input, IEnumerable changesSinceLastParse) + { + if (!Lock.IsWriteLockHeld) + throw new InvalidOperationException("Lock needed!"); + + // Use changes to invalidate cache + if (changesSinceLastParse != null) { + this.TrackedSegments.UpdateOffsetsAndInvalidate(changesSinceLastParse); + } else { + this.TrackedSegments.InvalidateAll(); + } + + TagReader tagReader = new TagReader(this, input); + List tags = tagReader.ReadAllTags(); + AXmlDocument parsedDocument = new TagMatchingHeuristics(this, input, tags).ReadDocument(); + tagReader.PrintStringCacheStats(); + AXmlParser.Log("Updating main DOM tree..."); + userDocument.UpdateTreeFrom(parsedDocument); + userDocument.DebugCheckConsistency(true); + Assert(userDocument.GetSelfAndAllChildren().Count() == parsedDocument.GetSelfAndAllChildren().Count(), "Parsed document and updated document have different number of children"); + return userDocument; + } + + /// + /// Makes calls to Parse() thread-safe. Use Lock everywhere Parse() is called. + /// + public ReaderWriterLockSlim Lock { get; private set; } + + /// + /// Returns the last cached version of the document. + /// + /// No read lock is held by the current thread. + public AXmlDocument LastDocument { + get { + if (!Lock.IsReadLockHeld) + throw new InvalidOperationException("Read lock needed!"); + + return userDocument; + } + } + + /// + /// Clears the parser data. + /// + /// No write lock is held by the current thread. + public void Clear() + { + if (!Lock.IsWriteLockHeld) + throw new InvalidOperationException("Write lock needed!"); + + ClearInternal(); + } + + void ClearInternal() + { + this.UnknownEntityReferenceIsError = true; + this.TrackedSegments = new TrackedSegmentCollection(); + this.userDocument = new AXmlDocument() { Parser = this }; + this.userDocument.Document = this.userDocument; + // Track the document + this.TrackedSegments.AddParsedObject(this.userDocument, null); + this.userDocument.IsCached = false; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs new file mode 100644 index 000000000..8283cea22 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs @@ -0,0 +1,108 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Represents any markup starting with "<" and (hopefully) ending with ">" + /// + public class AXmlTag: AXmlContainer + { + /// These identify the start of DTD elements + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification="ReadOnlyCollection is immutable")] + public static readonly ReadOnlyCollection DtdNames = new ReadOnlyCollection( + new string[] {" Opening bracket - usually "<" + public string OpeningBracket { get; internal set; } + /// Name following the opening bracket + public string Name { get; internal set; } + /// Opening bracket - usually ">" + public string ClosingBracket { get; internal set; } + + /// True if tag starts with "<" + public bool IsStartOrEmptyTag { get { return OpeningBracket == "<"; } } + /// True if tag starts with "<" and ends with ">" + public bool IsStartTag { get { return OpeningBracket == "<" && ClosingBracket == ">"; } } + /// True if tag starts with "<" and does not end with ">" + public bool IsEmptyTag { get { return OpeningBracket == "<" && ClosingBracket != ">" ; } } + /// True if tag starts with "</" + public bool IsEndTag { get { return OpeningBracket == " True if tag starts with "<?" + public bool IsProcessingInstruction { get { return OpeningBracket == " True if tag starts with "<!--" + public bool IsComment { get { return OpeningBracket == "") OnSyntaxError(tag, brStart, brEnd, "'-->' expected"); + } else if (tag.IsCData) { + if (tag.ClosingBracket != "]]>") OnSyntaxError(tag, brStart, brEnd, "']]>' expected"); + } else if (tag.IsProcessingInstruction) { + if (tag.ClosingBracket != "?>") OnSyntaxError(tag, brStart, brEnd, "'?>' expected"); + } else if (tag.IsUnknownBang) { + if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected"); + } else if (tag.IsDocumentType) { + if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected"); + } else { + throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket)); + } + + // Attribute name may not apper multiple times + var duplicates = tag.Children.OfType().GroupBy(attr => attr.Name).SelectMany(g => g.Skip(1)); + foreach(AXmlAttribute attr in duplicates) { + OnSyntaxError(tag, attr.StartOffset, attr.EndOffset, "Attribute with name '{0}' already exists", attr.Name); + } + + tag.EndOffset = this.CurrentLocation; + + OnParsed(tag); + return tag; + } + + /// + /// Reads any of the know opening brackets. (only full bracket) + /// Context: "<" + /// + string ReadOpeningBracket() + { + // We are using a lot of string literals so that the memory instances are shared + //int start = this.CurrentLocation; + if (TryRead('<')) { + if (TryRead('/')) { + return " + /// Reads any of the know closing brackets. (only full bracket) + /// Context: any + /// + bool TryReadClosingBracket(out string bracket) + { + // We are using a lot of string literals so that the memory instances are shared + if (TryRead('>')) { + bracket = ">"; + } else if (TryRead("/>")) { + bracket = "/>"; + } else if (TryRead("?>")) { + bracket = "?>"; + } else if (TryRead("-->")) { + bracket = "-->"; + } else if (TryRead("]]>")) { + bracket = "]]>"; + } else { + bracket = string.Empty; + return false; + } + return true; + } + + IEnumerable ReadContentOfDTD() + { + int start = this.CurrentLocation; + while(true) { + if (IsEndOfFile()) break; // End of file + TryMoveToNonWhiteSpace(); // Skip whitespace + if (TryRead('\'')) TryMoveTo('\''); // Skip single quoted string TODO: Bug + if (TryRead('\"')) TryMoveTo('\"'); // Skip single quoted string + if (TryRead('[')) { // Start of nested infoset + // Reading infoset + while(true) { + if (IsEndOfFile()) break; + TryMoveToAnyOf('<', ']'); + if (TryPeek('<')) { + if (start != this.CurrentLocation) { // Two following tags + yield return MakeText(start, this.CurrentLocation); + } + yield return ReadTag(); + start = this.CurrentLocation; + } + if (TryPeek(']')) break; + } + } + TryRead(']'); // End of nested infoset + if (TryPeek('>')) break; // Proper closing + if (TryPeek('<')) break; // Malformed XML + TryMoveNext(); // Skip anything else + } + if (start != this.CurrentLocation) { + yield return MakeText(start, this.CurrentLocation); + } + } + + /// + /// Context: name or "=\'\"" + /// + AXmlAttribute ReadAttribulte() + { + AssertHasMoreData(); + + AXmlAttribute attr; + if (TryReadFromCacheOrNew(out attr)) return attr; + + attr.StartOffset = this.CurrentLocation; + + // Read name + string name; + if (TryReadName(out name)) { + if (!IsValidName(name)) { + OnSyntaxError(attr, this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name); + } + } else { + OnSyntaxError(attr, "Attribute name expected"); + } + attr.Name = name; + + // Read equals sign and surrounding whitespace + int checkpoint = this.CurrentLocation; + TryMoveToNonWhiteSpace(); + if (TryRead('=')) { + int chk2 = this.CurrentLocation; + TryMoveToNonWhiteSpace(); + if (!TryPeek('"') && !TryPeek('\'')) { + // Do not read whitespace if quote does not follow + GoBack(chk2); + } + attr.EqualsSign = GetText(checkpoint, this.CurrentLocation); + } else { + GoBack(checkpoint); + OnSyntaxError(attr, "'=' expected"); + attr.EqualsSign = string.Empty; + } + + // Read attribute value + int start = this.CurrentLocation; + char quoteChar = TryPeek('"') ? '"' : '\''; + bool startsWithQuote; + if (TryRead(quoteChar)) { + startsWithQuote = true; + int valueStart = this.CurrentLocation; + TryMoveToAnyOf(quoteChar, '<'); + if (TryRead(quoteChar)) { + if (!TryPeekAnyOf(' ', '\t', '\n', '\r', '/', '>', '?')) { + if (TryPeekPrevious('=', 2) || (TryPeekPrevious('=', 3) && TryPeekPrevious(' ', 2))) { + // This actually most likely means that we are in the next attribute value + GoBack(valueStart); + ReadAttributeValue(quoteChar); + if (TryRead(quoteChar)) { + OnSyntaxError(attr, "White space or end of tag expected"); + } else { + OnSyntaxError(attr, "Quote {0} expected (or add whitespace after the following one)", quoteChar); + } + } else { + OnSyntaxError(attr, "White space or end of tag expected"); + } + } + } else { + // '<' or end of file + GoBack(valueStart); + ReadAttributeValue(quoteChar); + OnSyntaxError(attr, "Quote {0} expected", quoteChar); + } + } else { + startsWithQuote = false; + int valueStart = this.CurrentLocation; + ReadAttributeValue(null); + TryRead('\"'); + TryRead('\''); + if (valueStart == this.CurrentLocation) { + OnSyntaxError(attr, "Attribute value expected"); + } else { + OnSyntaxError(attr, valueStart, this.CurrentLocation, "Attribute value must be quoted"); + } + } + attr.QuotedValue = GetText(start, this.CurrentLocation); + attr.Value = Unquote(attr.QuotedValue); + attr.Value = Dereference(attr, attr.Value, startsWithQuote ? start + 1 : start); + + attr.EndOffset = this.CurrentLocation; + + OnParsed(attr); + return attr; + } + + /// + /// Read everything up to quote (excluding), opening/closing tag or attribute signature + /// + void ReadAttributeValue(char? quote) + { + while(true) { + if (IsEndOfFile()) return; + // What is next? + int start = this.CurrentLocation; + TryMoveToNonWhiteSpace(); // Read white space (if any) + if (quote.HasValue) { + if (TryPeek(quote.Value)) return; + } else { + if (TryPeek('"') || TryPeek('\'')) return; + } + // Opening/closing tag + string endBr; + if (TryPeek('<') || TryReadClosingBracket(out endBr)) { + GoBack(start); + return; + } + // Try reading attribute signature + string name; + if (TryReadName(out name)) { + int nameEnd = this.CurrentLocation; + if (TryMoveToNonWhiteSpace() && TryRead("=") && + TryMoveToNonWhiteSpace() && TryPeekAnyOf('"', '\'')) + { + // Start of attribute. Great + GoBack(start); + return; // Done + } else { + // Just some gargabe - make it part of the value + GoBack(nameEnd); + continue; // Read more + } + } + TryMoveNext(); // Accept everyting else + } + } + + AXmlText MakeText(int start, int end) + { + AXmlParser.DebugAssert(end > start, "Empty text"); + + AXmlText text = new AXmlText() { + StartOffset = start, + EndOffset = end, + EscapedValue = GetText(start, end), + Type = TextType.Other + }; + + OnParsed(text); + return text; + } + + const int maxEntityLength = 16; // The longest build-in one is 10 ("􏿿") + const int maxTextFragmentSize = 64; + const int lookAheadLength = (3 * maxTextFragmentSize) / 2; // More so that we do not get small "what was inserted" fragments + + /// + /// Reads text and optionaly separates it into fragments. + /// It can also return empty set for no appropriate text input. + /// Make sure you enumerate it only once + /// + IEnumerable ReadText(TextType type) + { + bool lookahead = false; + while(true) { + AXmlText text; + if (TryReadFromCacheOrNew(out text, t => t.Type == type)) { + // Cached text found + yield return text; + continue; // Read next fragment; the method can handle "no text left" + } + text.Type = type; + + // Limit the reading to just a few characters + // (the first character not to be read) + int fragmentEnd = Math.Min(this.CurrentLocation + maxTextFragmentSize, this.InputLength); + + // Look if some futher text has been already processed and align so that + // we hit that chache point. It is expensive so it is off for the first run + if (lookahead) { + // Note: Must fit entity + AXmlObject nextFragment = trackedSegments.GetCachedObject(this.CurrentLocation + maxEntityLength, lookAheadLength - maxEntityLength, t => t.Type == type); + if (nextFragment != null) { + fragmentEnd = Math.Min(nextFragment.StartOffset, this.InputLength); + AXmlParser.Log("Parsing only text ({0}-{1}) because later text was already processed", this.CurrentLocation, fragmentEnd); + } + } + lookahead = true; + + text.StartOffset = this.CurrentLocation; + int start = this.CurrentLocation; + + // Whitespace would be skipped anyway by any operation + TryMoveToNonWhiteSpace(fragmentEnd); + int wsEnd = this.CurrentLocation; + + // Try move to the terminator given by the context + if (type == TextType.WhiteSpace) { + TryMoveToNonWhiteSpace(fragmentEnd); + } else if (type == TextType.CharacterData) { + while(true) { + if (!TryMoveToAnyOf(new char[] {'<', ']'}, fragmentEnd)) break; // End of fragment + if (TryPeek('<')) break; + if (TryPeek(']')) { + if (TryPeek("]]>")) { + OnSyntaxError(text, this.CurrentLocation, this.CurrentLocation + 3, "']]>' is not allowed in text"); + } + TryMoveNext(); + continue; + } + throw new Exception("Infinite loop"); + } + } else if (type == TextType.Comment) { + // Do not report too many errors + bool errorReported = false; + while(true) { + if (!TryMoveTo('-', fragmentEnd)) break; // End of fragment + if (TryPeek("-->")) break; + if (TryPeek("--") && !errorReported) { + OnSyntaxError(text, this.CurrentLocation, this.CurrentLocation + 2, "'--' is not allowed in comment"); + errorReported = true; + } + TryMoveNext(); + } + } else if (type == TextType.CData) { + while(true) { + // We can not use use TryMoveTo("]]>", fragmentEnd) because it may incorectly accept "]" at the end of fragment + if (!TryMoveTo(']', fragmentEnd)) break; // End of fragment + if (TryPeek("]]>")) break; + TryMoveNext(); + } + } else if (type == TextType.ProcessingInstruction) { + while(true) { + if (!TryMoveTo('?', fragmentEnd)) break; // End of fragment + if (TryPeek("?>")) break; + TryMoveNext(); + } + } else if (type == TextType.UnknownBang) { + TryMoveToAnyOf(new char[] {'<', '>'}, fragmentEnd); + } else { + throw new Exception("Uknown type " + type); + } + + text.ContainsOnlyWhitespace = (wsEnd == this.CurrentLocation); + + // Terminal found or real end was reached; + bool finished = this.CurrentLocation < fragmentEnd || IsEndOfFile(); + + if (!finished) { + // We have to continue reading more text fragments + + // If there is entity reference, make sure the next segment starts with it to prevent framentation + int entitySearchStart = Math.Max(start + 1 /* data for us */, this.CurrentLocation - maxEntityLength); + int entitySearchLength = this.CurrentLocation - entitySearchStart; + if (entitySearchLength > 0) { + // Note that LastIndexOf works backward + int entityIndex = input.LastIndexOf('&', this.CurrentLocation - 1, entitySearchLength); + if (entityIndex != -1) { + GoBack(entityIndex); + } + } + } + + text.EscapedValue = GetText(start, this.CurrentLocation); + if (type == TextType.CharacterData) { + // Normalize end of line first + text.Value = Dereference(text, NormalizeEndOfLine(text.EscapedValue), start); + } else { + text.Value = text.EscapedValue; + } + text.EndOffset = this.CurrentLocation; + + if (text.EscapedValue.Length > 0) { + OnParsed(text); + yield return text; + } + + if (finished) { + yield break; + } + } + } + + #region Helper methods + + void OnSyntaxError(AXmlObject obj, string message, params object[] args) + { + OnSyntaxError(obj, this.CurrentLocation, this.CurrentLocation + 1, message, args); + } + + public static void OnSyntaxError(AXmlObject obj, int start, int end, string message, params object[] args) + { + if (end <= start) end = start + 1; + string formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args); + AXmlParser.Log("Syntax error ({0}-{1}): {2}", start, end, formattedMessage); + obj.AddSyntaxError(new SyntaxError() { + Object = obj, + StartOffset = start, + EndOffset = end, + Message = formattedMessage, + }); + } + + static bool IsValidName(string name) + { + try { + System.Xml.XmlConvert.VerifyName(name); + return true; + } catch (System.Xml.XmlException) { + return false; + } + } + + /// Remove quoting from the given string + static string Unquote(string quoted) + { + if (string.IsNullOrEmpty(quoted)) return string.Empty; + char first = quoted[0]; + if (quoted.Length == 1) return (first == '"' || first == '\'') ? string.Empty : quoted; + char last = quoted[quoted.Length - 1]; + if (first == '"' || first == '\'') { + if (first == last) { + // Remove both quotes + return quoted.Substring(1, quoted.Length - 2); + } else { + // Remove first quote + return quoted.Remove(0, 1); + } + } else { + if (last == '"' || last == '\'') { + // Remove last quote + return quoted.Substring(0, quoted.Length - 1); + } else { + // Keep whole string + return quoted; + } + } + } + + static string NormalizeEndOfLine(string text) + { + return text.Replace("\r\n", "\n").Replace("\r", "\n"); + } + + string Dereference(AXmlObject owner, string text, int textLocation) + { + StringBuilder sb = null; // The dereferenced text so far (all up to 'curr') + int curr = 0; + while(true) { + // Reached end of input + if (curr == text.Length) { + if (sb != null) { + return sb.ToString(); + } else { + return text; + } + } + + // Try to find reference + int start = text.IndexOf('&', curr); + + // No more references found + if (start == -1) { + if (sb != null) { + sb.Append(text, curr, text.Length - curr); // Add rest + return sb.ToString(); + } else { + return text; + } + } + + // Append text before the enitiy reference + if (sb == null) sb = new StringBuilder(text.Length); + sb.Append(text, curr, start - curr); + curr = start; + + // Process the entity + int errorLoc = textLocation + sb.Length; + + // Find entity name + int end = text.IndexOfAny(new char[] {'&', ';'}, start + 1, Math.Min(maxEntityLength, text.Length - (start + 1))); + if (end == -1 || text[end] == '&') { + // Not found + OnSyntaxError(owner, errorLoc, errorLoc + 1, "Entity reference must be terminated with ';'"); + // Keep '&' + sb.Append('&'); + curr++; + continue; // Restart and next character location + } + string name = text.Substring(start + 1, end - (start + 1)); + + // Resolve the name + string replacement; + if (name.Length == 0) { + replacement = null; + OnSyntaxError(owner, errorLoc + 1, errorLoc + 1, "Entity name expected"); + } else if (name == "amp") { + replacement = "&"; + } else if (name == "lt") { + replacement = "<"; + } else if (name == "gt") { + replacement = ">"; + } else if (name == "apos") { + replacement = "'"; + } else if (name == "quot") { + replacement = "\""; + } else if (name.Length > 0 && name[0] == '#') { + int num; + if (name.Length > 1 && name[1] == 'x') { + if (!int.TryParse(name.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out num)) { + num = -1; + OnSyntaxError(owner, errorLoc + 3, errorLoc + 1 + name.Length, "Hexadecimal code of unicode character expected"); + } + } else { + if (!int.TryParse(name.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out num)) { + num = -1; + OnSyntaxError(owner, errorLoc + 2, errorLoc + 1 + name.Length, "Numeric code of unicode character expected"); + } + } + if (num != -1) { + try { + replacement = char.ConvertFromUtf32(num); + } catch (ArgumentOutOfRangeException) { + replacement = null; + OnSyntaxError(owner, errorLoc + 2, errorLoc + 1 + name.Length, "Invalid unicode character U+{0:X} ({0})", num); + } + } else { + replacement = null; + } + } else if (!IsValidName(name)) { + replacement = null; + OnSyntaxError(owner, errorLoc + 1, errorLoc + 1, "Invalid entity name"); + } else { + replacement = null; + if (parser.UnknownEntityReferenceIsError) { + OnSyntaxError(owner, errorLoc, errorLoc + 1 + name.Length + 1, "Unknown entity reference '{0}'", name); + } + } + + // Append the replacement to output + if (replacement != null) { + sb.Append(replacement); + } else { + sb.Append('&'); + sb.Append(name); + sb.Append(';'); + } + curr = end + 1; + continue; + } + } + + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs new file mode 100644 index 000000000..965185925 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs @@ -0,0 +1,39 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// Identifies the context in which the text occured + enum TextType + { + /// Ends with non-whitespace + WhiteSpace, + + /// Ends with "<"; "]]>" is error + CharacterData, + + /// Ends with "-->"; "--" is error + Comment, + + /// Ends with "]]>" + CData, + + /// Ends with "?>" + ProcessingInstruction, + + /// Ends with "<" or ">" + UnknownBang, + + /// Unknown + Other + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs new file mode 100644 index 000000000..83d3315ed --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs @@ -0,0 +1,309 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Tango.Scripting.Editors.Xml +{ + class TokenReader + { + string input; + int inputLength; + int currentLocation; + + // CurrentLocation is assumed to be touched and the fact does not + // have to be recorded in this variable. + // This stores any value bigger than that if applicable. + // Acutal value is max(currentLocation, maxTouchedLocation). + int maxTouchedLocation; + + public int InputLength { + get { return inputLength; } + } + + public int CurrentLocation { + get { return currentLocation; } + } + + public int MaxTouchedLocation { + get { return Math.Max(currentLocation, maxTouchedLocation); } + } + + public TokenReader(string input) + { + this.input = input; + this.inputLength = input.Length; + } + + protected bool IsEndOfFile() + { + return currentLocation == inputLength; + } + + protected bool HasMoreData() + { + return currentLocation < inputLength; + } + + protected void AssertHasMoreData() + { + AXmlParser.Assert(HasMoreData(), "Unexpected end of file"); + } + + protected bool TryMoveNext() + { + if (currentLocation == inputLength) return false; + + currentLocation++; + return true; + } + + protected void Skip(int count) + { + AXmlParser.Assert(currentLocation + count <= inputLength, "Skipping after the end of file"); + currentLocation += count; + } + + protected void GoBack(int oldLocation) + { + AXmlParser.Assert(oldLocation <= currentLocation, "Trying to move forward"); + maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation); + currentLocation = oldLocation; + } + + protected bool TryRead(char c) + { + if (currentLocation == inputLength) return false; + + if (input[currentLocation] == c) { + currentLocation++; + return true; + } else { + return false; + } + } + + protected bool TryReadAnyOf(params char[] c) + { + if (currentLocation == inputLength) return false; + + if (c.Contains(input[currentLocation])) { + currentLocation++; + return true; + } else { + return false; + } + } + + protected bool TryRead(string text) + { + if (TryPeek(text)) { + currentLocation += text.Length; + return true; + } else { + return false; + } + } + + protected bool TryPeekPrevious(char c, int back) + { + if (currentLocation - back == inputLength) return false; + if (currentLocation - back < 0 ) return false; + + return input[currentLocation - back] == c; + } + + protected bool TryPeek(char c) + { + if (currentLocation == inputLength) return false; + + return input[currentLocation] == c; + } + + protected bool TryPeekAnyOf(params char[] chars) + { + if (currentLocation == inputLength) return false; + + return chars.Contains(input[currentLocation]); + } + + protected bool TryPeek(string text) + { + if (!TryPeek(text[0])) return false; // Early exit + + maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + (text.Length - 1)); + // The following comparison 'touches' the end of file - it does depend on the end being there + if (currentLocation + text.Length > inputLength) return false; + + return input.Substring(currentLocation, text.Length) == text; + } + + protected bool TryPeekWhiteSpace() + { + if (currentLocation == inputLength) return false; + + char c = input[currentLocation]; + return ((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r'); + } + + // The move functions do not have to move if already at target + // The move functions allow 'overriding' of the document length + + protected bool TryMoveTo(char c) + { + return TryMoveTo(c, inputLength); + } + + protected bool TryMoveTo(char c, int inputLength) + { + if (currentLocation == inputLength) return false; + int index = input.IndexOf(c, currentLocation, inputLength - currentLocation); + if (index != -1) { + currentLocation = index; + return true; + } else { + currentLocation = inputLength; + return false; + } + } + + protected bool TryMoveToAnyOf(params char[] c) + { + return TryMoveToAnyOf(c, inputLength); + } + + protected bool TryMoveToAnyOf(char[] c, int inputLength) + { + if (currentLocation == inputLength) return false; + int index = input.IndexOfAny(c, currentLocation, inputLength - currentLocation); + if (index != -1) { + currentLocation = index; + return true; + } else { + currentLocation = inputLength; + return false; + } + } + + protected bool TryMoveTo(string text) + { + return TryMoveTo(text, inputLength); + } + + protected bool TryMoveTo(string text, int inputLength) + { + if (currentLocation == inputLength) return false; + int index = input.IndexOf(text, currentLocation, inputLength - currentLocation, StringComparison.Ordinal); + if (index != -1) { + maxTouchedLocation = index + text.Length - 1; + currentLocation = index; + return true; + } else { + currentLocation = inputLength; + return false; + } + } + + protected bool TryMoveToNonWhiteSpace() + { + return TryMoveToNonWhiteSpace(inputLength); + } + + protected bool TryMoveToNonWhiteSpace(int inputLength) + { + while(true) { + if (currentLocation == inputLength) return false; // Reject end of file + char c = input[currentLocation]; + if (((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) { + currentLocation++; // Accept white-space + continue; + } else { + return true; // Found non-white-space + } + } + } + + /// + /// Read a name token. + /// The following characters are not allowed: + /// "" End of file + /// " \n\r\t" Whitesapce + /// "=\'\"" Attribute value + /// "<>/?" Tags + /// + /// True if read at least one character + protected bool TryReadName(out string res) + { + int start = currentLocation; + // Keep reading up to invalid character + while(true) { + if (currentLocation == inputLength) break; // Reject end of file + char c = input[currentLocation]; + if (0x41 <= (int)c) { // Accpet from 'A' onwards + currentLocation++; + continue; + } + if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || // Reject whitesapce + c == '=' || c == '\'' || c == '"' || // Reject attributes + c == '<' || c == '>' || c == '/' || c == '?') { // Reject tags + break; + } else { + currentLocation++; + continue; // Accept other character + } + } + if (start == currentLocation) { + res = string.Empty; + return false; + } else { + res = GetText(start, currentLocation); + return true; + } + } + + protected string GetText(int start, int end) + { + AXmlParser.Assert(end <= currentLocation, "Reading ahead of current location"); + if (start == inputLength && end == inputLength) { + return string.Empty; + } else { + return GetCachedString(input.Substring(start, end - start)); + } + } + + Dictionary stringCache = new Dictionary(); + int stringCacheRequestedCount; + int stringCacheRequestedSize; + int stringCacheStoredCount; + int stringCacheStoredSize; + + string GetCachedString(string cached) + { + stringCacheRequestedCount += 1; + stringCacheRequestedSize += 8 + 2 * cached.Length; + // Do not bother with long strings + if (cached.Length > 32) { + stringCacheStoredCount += 1; + stringCacheStoredSize += 8 + 2 * cached.Length; + return cached; + } + if (stringCache.ContainsKey(cached)) { + // Get the instance from the cache instead + return stringCache[cached]; + } else { + // Add to cache + stringCacheStoredCount += 1; + stringCacheStoredSize += 8 + 2 * cached.Length; + stringCache.Add(cached, cached); + return cached; + } + } + + public void PrintStringCacheStats() + { + AXmlParser.Log("String cache: Requested {0} ({1} bytes); Actaully stored {2} ({3} bytes); {4}% stored", stringCacheRequestedCount, stringCacheRequestedSize, stringCacheStoredCount, stringCacheStoredSize, stringCacheRequestedSize == 0 ? 0 : stringCacheStoredSize * 100 / stringCacheRequestedSize); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs new file mode 100644 index 000000000..f8e516591 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs @@ -0,0 +1,165 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +using Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.Xml +{ + /// + /// Holds all objects that need to keep offsets up to date. + /// + class TrackedSegmentCollection + { + /// + /// Holds all types of objects in one collection. + /// + TextSegmentCollection segments = new TextSegmentCollection(); + + /// + /// Is used to identify what memory range was touched by object + /// The default is (StartOffset, EndOffset + 1) which is not stored + /// + class TouchedRange: TextSegment + { + public AXmlObject TouchedByObject { get; set; } + } + + public void UpdateOffsetsAndInvalidate(IEnumerable changes) + { + foreach(DocumentChangeEventArgs change in changes) { + // Update offsets of all items + segments.UpdateOffsets(change); + + // Remove any items affected by the change + AXmlParser.Log("Changed {0}-{1}", change.Offset, change.Offset + change.InsertionLength); + // Removing will cause one of the ends to be set to change.Offset + // FindSegmentsContaining includes any segments touching + // so that conviniently takes care of the +1 byte + var segmentsContainingOffset = segments.FindOverlappingSegments(change.Offset, change.InsertionLength); + foreach(AXmlObject obj in segmentsContainingOffset.OfType().Where(o => o.IsCached)) { + InvalidateCache(obj, false); + } + foreach(TouchedRange range in segmentsContainingOffset.OfType()) { + AXmlParser.Log("Found that {0} dependeds on ({1}-{2})", range.TouchedByObject, range.StartOffset, range.EndOffset); + InvalidateCache(range.TouchedByObject, true); + segments.Remove(range); + } + } + } + + /// + /// Invlidates all objects. That is, the whole document has changed. + /// + /// We still have to keep the items becuase they might be in the document + public void InvalidateAll() + { + AXmlParser.Log("Invalidating all objects"); + foreach(AXmlObject obj in segments.OfType()) { + obj.IsCached = false; + } + } + + /// Add object to cache, optionally adding extra memory tracking + public void AddParsedObject(AXmlObject obj, int? maxTouchedLocation) + { + if (!(obj.Length > 0 || obj is AXmlDocument)) + AXmlParser.Assert(false, string.Format(CultureInfo.InvariantCulture, "Invalid object {0}. It has zero length.", obj)); +// // Expensive check +// if (obj is AXmlContainer) { +// int objStartOffset = obj.StartOffset; +// int objEndOffset = obj.EndOffset; +// foreach(AXmlObject child in ((AXmlContainer)obj).Children) { +// AXmlParser.Assert(objStartOffset <= child.StartOffset && child.EndOffset <= objEndOffset, "Wrong nesting"); +// } +// } + segments.Add(obj); + AddSyntaxErrorsOf(obj); + obj.IsCached = true; + if (maxTouchedLocation != null) { + // location is assumed to be read so the range ends at (location + 1) + // For example eg for "a_" it is (0-2) + TouchedRange range = new TouchedRange() { + StartOffset = obj.StartOffset, + EndOffset = maxTouchedLocation.Value + 1, + TouchedByObject = obj + }; + segments.Add(range); + AXmlParser.Log("{0} touched range ({1}-{2})", obj, range.StartOffset, range.EndOffset); + } + } + + /// Removes object with all of its non-cached children + public void RemoveParsedObject(AXmlObject obj) + { + // Cached objects may be used in the future - do not remove them + if (obj.IsCached) return; + segments.Remove(obj); + RemoveSyntaxErrorsOf(obj); + AXmlParser.Log("Stopped tracking {0}", obj); + + AXmlContainer container = obj as AXmlContainer; + if (container != null) { + foreach (AXmlObject child in container.Children) { + RemoveParsedObject(child); + } + } + } + + public void AddSyntaxErrorsOf(AXmlObject obj) + { + foreach(SyntaxError syntaxError in obj.MySyntaxErrors) { + segments.Add(syntaxError); + } + } + + public void RemoveSyntaxErrorsOf(AXmlObject obj) + { + foreach(SyntaxError syntaxError in obj.MySyntaxErrors) { + segments.Remove(syntaxError); + } + } + + IEnumerable FindParents(AXmlObject child) + { + int childStartOffset = child.StartOffset; + int childEndOffset = child.EndOffset; + foreach(AXmlObject parent in segments.FindSegmentsContaining(child.StartOffset).OfType()) { + // Parent is anyone wholy containg the child + if (parent.StartOffset <= childStartOffset && childEndOffset <= parent.EndOffset && parent != child) { + yield return parent; + } + } + } + + /// Invalidates items, but keeps tracking them + /// Can be called redundantly (from range tacking) + void InvalidateCache(AXmlObject obj, bool includeParents) + { + if (includeParents) { + foreach(AXmlObject parent in FindParents(obj)) { + parent.IsCached = false; + AXmlParser.Log("Invalidating cached item {0} (it is parent)", parent); + } + } + obj.IsCached = false; + AXmlParser.Log("Invalidating cached item {0}", obj); + } + + public T GetCachedObject(int offset, int lookaheadCount, Predicate conditon) where T: AXmlObject, new() + { + TextSegment obj = segments.FindFirstSegmentWithStartAfter(offset); + while(obj != null && offset <= obj.StartOffset && obj.StartOffset <= offset + lookaheadCount) { + if (obj is T && ((AXmlObject)obj).IsCached && conditon((T)obj)) { + return (T)obj; + } + obj = segments.GetNextSegment(obj); + } + return null; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config new file mode 100644 index 000000000..d3a17b4de --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config new file mode 100644 index 000000000..00eef19db --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.config b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.config new file mode 100644 index 000000000..de2319b52 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.config @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml new file mode 100644 index 000000000..be9fdd055 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml.cs new file mode 100644 index 000000000..273731b71 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace Tango.Scripting.IDE.UI +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml new file mode 100644 index 000000000..032d38c81 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml.cs new file mode 100644 index 000000000..13f2bf7a9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindow.xaml.cs @@ -0,0 +1,30 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE.UI +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : MetroWindow + { + public MainWindow() + { + InitializeComponent(); + DataContext = new MainWindowVM(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindowVM.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindowVM.cs new file mode 100644 index 000000000..2ced386b3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/MainWindowVM.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE.UI +{ + public class MainWindowVM : ViewModel + { + public ScriptIDEViewVM IdeVM { get; set; } + + public MainWindowVM() + { + IdeVM = new ScriptIDEViewVM(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..462c7e284 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +[assembly: AssemblyTitle("Tango - Scripting IDE Application")] +[assembly: ComVisible(false)] +[assembly: AssemblyVersion("2.0.36.1608")] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.Designer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.Designer.cs new file mode 100644 index 000000000..45fc62ce0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tango.Scripting.IDE.UI.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tango.Scripting.IDE.UI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.resx b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.Designer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.Designer.cs new file mode 100644 index 000000000..fb153fa4c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tango.Scripting.IDE.UI.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.settings b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Tango.Scripting.IDE.UI.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Tango.Scripting.IDE.UI.csproj new file mode 100644 index 000000000..e4702273a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/Tango.Scripting.IDE.UI.csproj @@ -0,0 +1,208 @@ + + + + + Debug + AnyCPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6} + WinExe + Tango.Scripting.IDE.UI + Tango.Scripting.IDE.UI + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + ..\..\Build\Scripting\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\..\Build\Scripting\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll + + + ..\..\packages\MahApps.Metro.1.5.0\lib\net45\MahApps.Metro.dll + + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll + + + + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + + ..\..\packages\MahApps.Metro.1.5.0\lib\net45\System.Windows.Interactivity.dll + + + + + + + + + 4.0 + + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + GlobalVersionInfo.cs + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {8491d07b-c1f6-4b62-a412-41b9fd2d6538} + Tango.SharedUI + + + {da62fa39-668b-47a6-b0f2-d2c1daf777b0} + Tango.Scripting.Editors + + + {c9f60285-91fb-4293-bcf5-164d76755cdd} + Tango.Scripting.IDE + + + {1e938fd2-c669-4738-98c9-77f96ce4d451} + Tango.Scripting + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/packages.config b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/packages.config new file mode 100644 index 000000000..5fa98b5e2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE.UI/packages.config @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/ErrorData.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/ErrorData.cs new file mode 100644 index 000000000..2dc07ba3f --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/ErrorData.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.Scripting.IDE.Controls +{ + public class ErrorData: ExtendedObject + { + private string _severity; + private string _error; + private string _project; + private string _code; + + public string Description + { + get { return _error; } + set + { + _error = value; + RaisePropertyChangedAuto(); + } + } + public string Severity + { + get { return _severity; } + set + { + _severity = value; + RaisePropertyChangedAuto(); + } + } + public string Project + { + get { return _project; } + set + { + _project = value; + RaisePropertyChangedAuto(); + } + } + public string Code + { + get { return _code; } + set { _code = value; } + } + + public string File { get; set; } + public string Line { get; set; } + public string SuppressionState { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SharedResourceDictionary.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SharedResourceDictionary.cs new file mode 100644 index 000000000..a163355c4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SharedResourceDictionary.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +namespace Tango.Scripting.IDE.Controls +{ + + /// + /// The shared resource dictionary is a specialized resource dictionary + /// that loads it content only once. If a second instance with the same source + /// is created, it only merges the resources from the cache. + /// + public class SharedResourceDictionary : ResourceDictionary + { + /// + /// Internal cache of loaded dictionaries + /// + public static Dictionary _sharedDictionaries = + new Dictionary(); + + /// + /// Local member of the source uri + /// + private Uri _sourceUri; + + /// + /// Gets or sets the uniform resource identifier (URI) to load resources from. + /// + public new Uri Source + { + get { return _sourceUri; } + set + { + _sourceUri = value; + + if (!_sharedDictionaries.ContainsKey(value)) + { + // If the dictionary is not yet loaded, load it by setting + // the source of the base class + base.Source = value; + + // add it to the cache + _sharedDictionaries.Add(value, this); + } + else + { + // If the dictionary is already loaded, get it from the cache + MergedDictionaries.Add(_sharedDictionaries[value]); + } + } + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SolutionItemControl.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SolutionItemControl.cs new file mode 100644 index 000000000..ad6c65f22 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/SolutionItemControl.cs @@ -0,0 +1,52 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE.Controls +{ + public class SolutionItemControl : Control + { + public ISolutionItem SolutionItem + { + get { return (ISolutionItem)GetValue(SolutionItemProperty); } + set { SetValue(SolutionItemProperty, value); } + } + public static readonly DependencyProperty SolutionItemProperty = + DependencyProperty.Register("SolutionItem", typeof(ISolutionItem), typeof(SolutionItemControl), new PropertyMetadata(null)); + + public ICommand OpenCommand + { + get { return (ICommand)GetValue(OpenCommandProperty); } + set { SetValue(OpenCommandProperty, value); } + } + public static readonly DependencyProperty OpenCommandProperty = + DependencyProperty.Register("OpenCommand", typeof(ICommand), typeof(SolutionItemControl), new PropertyMetadata(null)); + + static SolutionItemControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SolutionItemControl), new FrameworkPropertyMetadata(typeof(SolutionItemControl))); + } + + public SolutionItemControl() + { + PreviewMouseDoubleClick += (_, __) => + { + if (SolutionItem.CanOpen) + { + OpenCommand?.Execute(SolutionItem); + } + }; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/TabConrolClose.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/TabConrolClose.cs new file mode 100644 index 000000000..7130733e5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Controls/TabConrolClose.cs @@ -0,0 +1,26 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE.Controls +{ + + public class TabConrolClose : TabControl + { + static TabConrolClose() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TabConrolClose), new FrameworkPropertyMetadata(typeof(TabConrolClose))); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Converters/LeftMarginMultiplierConverter.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Converters/LeftMarginMultiplierConverter.cs new file mode 100644 index 000000000..2acefc09c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Converters/LeftMarginMultiplierConverter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace Tango.Scripting.IDE.Converters +{ + public static class TreeViewItemExtensions + { + public static int GetDepth(this TreeViewItem item) + { + TreeViewItem parent; + while ((parent = GetParent(item)) != null) + { + return GetDepth(parent) + 1; + } + return 0; + } + + private static TreeViewItem GetParent(TreeViewItem item) + { + var parent = VisualTreeHelper.GetParent(item); + while (!(parent is TreeViewItem || parent is TreeView)) + { + parent = VisualTreeHelper.GetParent(parent); + } + return parent as TreeViewItem; + } + } + public class LeftMarginMultiplierConverter : IValueConverter + { + public double Length { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var item = value as TreeViewItem; + if (item == null) + return new Thickness(0); + + return new Thickness(Length * item.GetDepth(), 0, 0, 0); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml new file mode 100644 index 000000000..1df26b2e6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name: + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml.cs new file mode 100644 index 000000000..83856f4e7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectView.xaml.cs @@ -0,0 +1,28 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE.Dialogs +{ + /// + /// Interaction logic for AddProjectControl.xaml + /// + public partial class AddProjectView : UserControl + { + public AddProjectView() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectViewVM.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectViewVM.cs new file mode 100644 index 000000000..2d636a6d5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/AddProjectViewVM.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE.Dialogs +{ + public class AddProjectViewVM : BaseProjectDialogVM + { + public AddProjectViewVM() : base() + { + Title = "Add Project"; + //ProjectName + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/BaseProjectDialogVM.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/BaseProjectDialogVM.cs new file mode 100644 index 000000000..7742a3434 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/BaseProjectDialogVM.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.Core.Commands; +using Tango.Scripting.IDE.ProjectTypes; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE.Dialogs +{ + public class BaseProjectDialogVM : IDEDialogViewModel + { + #region properties + + public ObservableCollection ProjectTypes { get; set; } + + private IProjectType _selectedProjectType = null; + public IProjectType SelectedProjectType + { + get { return _selectedProjectType; } + set + { + _selectedProjectType = value; + RaisePropertyChangedAuto(); + RaisePropertyChanged("LargeImage"); + RaisePropertyChanged("SelectedDescription"); + } + } + private BitmapSource _defaultLargeImage = ProjectType.GetImage("Images/test.png"); + public BitmapSource LargeImage + { + get + { + if (SelectedProjectType != null) + return SelectedProjectType.LargeImage; + else return _defaultLargeImage; + } + } + public string SelectedDescription + { + get + { + if (SelectedProjectType != null) + return SelectedProjectType.Description; + return ""; + } + } + private String _projectName = "App1"; + public String ProjectName + { + get { return _projectName; } + set { _projectName = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + private String _projectLocation; + public String ProjectLocation + { + get { return _projectLocation; } + set { _projectLocation = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + #endregion + + #region constructor + + public BaseProjectDialogVM() : base() + { + ProjectTypes = new ObservableCollection(); + RegisterProjectType(new StubProjectType()); + RegisterProjectType(new UnitTestProjectType()); + + _selectedProjectType = ProjectTypes.FirstOrDefault(); + + string workingDirectory = Environment.CurrentDirectory; + ProjectLocation = Directory.GetParent(workingDirectory).Parent.Parent.FullName; + } + + #endregion + + #region register_project_types + + public void RegisterProjectType(IProjectType projectType) + { + ProjectTypes.Add(projectType); + } + + public void UnRegisterProjectItemHandler(IProjectType projectType) + { + ProjectTypes.Remove(projectType); + } + + #endregion + + #region Override Methods + + protected override bool CanOK() + { + return !String.IsNullOrWhiteSpace(ProjectLocation) && !String.IsNullOrWhiteSpace(ProjectName); + } + + #endregion + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml new file mode 100644 index 000000000..2a6842745 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name: + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml.cs new file mode 100644 index 000000000..5221146d8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectView.xaml.cs @@ -0,0 +1,29 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE.Dialogs +{ + /// + /// Interaction logic for AddNewControl.xaml + /// + public partial class NewProjectView : UserControl + { + public NewProjectView() + { + InitializeComponent(); + } + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectViewVM.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectViewVM.cs new file mode 100644 index 000000000..69cf8034e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Dialogs/NewProjectViewVM.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.Core.Commands; +using Tango.Scripting.IDE.ProjectTypes; +using Tango.SharedUI; +using Microsoft.WindowsAPICodePack.Dialogs; +using Microsoft.Win32; + +namespace Tango.Scripting.IDE.Dialogs +{ + public class NewProjectViewVM : BaseProjectDialogVM + { + /// + /// Gets or sets the last solution locations. + /// + public ObservableCollection LastSolutionPaths { get; set; } + + private String _solutionName = "App1"; + public String SolutionName + { + get { return _solutionName; } + set { _solutionName = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + #region Commands + public RelayCommand BrowseFileCommand { get; set; } + #endregion + + public NewProjectViewVM() : base() + { + Title = "New Project"; + BrowseFileCommand = new RelayCommand(BrowseFile); + } + + public NewProjectViewVM(IEnumerable lastSolutionFolders) : this() + { + LastSolutionPaths = new ObservableCollection(lastSolutionFolders); + ProjectLocation = LastSolutionPaths.FirstOrDefault(); + } + + private void BrowseFile() + { + CommonOpenFileDialog dialog = new CommonOpenFileDialog(); + dialog.InitialDirectory = LastSolutionPaths.LastOrDefault(); + dialog.IsFolderPicker = true; + if (dialog.ShowDialog() == CommonFileDialogResult.Ok) + { + if (Directory.Exists(dialog.FileName) == true) + { + if (false == LastSolutionPaths.Contains(dialog.FileName)) + { + LastSolutionPaths.Add(dialog.FileName); + } + ProjectLocation = dialog.FileName; + } + } + } + + protected override bool CanOK() + { + return base.CanOK() && !String.IsNullOrWhiteSpace(SolutionName); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEDialogViewModel.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEDialogViewModel.cs new file mode 100644 index 000000000..7532581b1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEDialogViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE +{ + public class IDEDialogViewModel : DialogViewVM + { + public String Title { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDESettings.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDESettings.cs new file mode 100644 index 000000000..347b06468 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDESettings.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Settings; + +namespace Tango.Scripting.IDE +{ + public class IDESettings : SettingsBase + { + public List LastSolutionLocations { get; set; } + + public IDESettings() + { + LastSolutionLocations = new List(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEViewModel.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEViewModel.cs new file mode 100644 index 000000000..b2d0b85b5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IDEViewModel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core.DI; +using Tango.Scripting.IDE.Notifications; +using Tango.Settings; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE +{ + public class IDEViewModel : ViewModel + { + public INotificationManager NotificationManager { get; set; } + + public IDESettings Settings { get; set; } + + public IDEViewModel() + { + TangoIOC.Default.Inject(this); + Settings = SettingsManager.Default.GetOrCreate(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProject.cs new file mode 100644 index 000000000..18d95a7d8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProject.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE +{ + public interface IProject : ISolutionItem + { + String FilePath { get; set; } + + String WorkingFolder { get; } + + ObservableCollection Items { get; set; } + + Task Build(); + + Task Run(); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectItem.cs new file mode 100644 index 000000000..0d34cf2e6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectItem.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE +{ + public interface IProjectItem : ISolutionItem + { + ObservableCollection Items { get; set; } + + FrameworkElement View { get; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectType.cs new file mode 100644 index 000000000..19a234db3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/IProjectType.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE +{ + public interface IProjectType + { + IProject NewProject(String projectPath); + + String Name { get; } + + String Description { get; } + + BitmapSource SmallImage { get; } + BitmapSource LargeImage { get; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ISolutionItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ISolutionItem.cs new file mode 100644 index 000000000..74bdab066 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ISolutionItem.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE +{ + public interface ISolutionItem + { + String Name { get; } + + BitmapSource Image { get; } + + bool CanOpen { get; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpProject.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpProject.png new file mode 100644 index 000000000..5b0f29960 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpProject.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpScriptItem.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpScriptItem.png new file mode 100644 index 000000000..37e4e1727 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CSharpScriptItem.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseDocument_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseDocument_16x.png new file mode 100644 index 000000000..5c5691574 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseDocument_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseSolution_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseSolution_16x.png new file mode 100644 index 000000000..41ff45a94 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/CloseSolution_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/FindinFiles_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/FindinFiles_16x.png new file mode 100644 index 000000000..5b47a34ce Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/FindinFiles_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewFileCollection_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewFileCollection_16x.png new file mode 100644 index 000000000..bc5cef828 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewFileCollection_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewRelationship_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewRelationship_16x.png new file mode 100644 index 000000000..0be7b4fbf Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewRelationship_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewWindow_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewWindow_16x.png new file mode 100644 index 000000000..d4d0c0329 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/NewWindow_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/OpenFolder_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/OpenFolder_16x.png new file mode 100644 index 000000000..9ba3e52d2 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/OpenFolder_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Pause_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Pause_16x.png new file mode 100644 index 000000000..10cdfb53c Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Pause_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Redo_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Redo_16x.png new file mode 100644 index 000000000..331e15706 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Redo_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Reference.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Reference.png new file mode 100644 index 000000000..fa5430947 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Reference.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveAll_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveAll_16x.png new file mode 100644 index 000000000..aaffd1174 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveAll_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveClose_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveClose_16x.png new file mode 100644 index 000000000..09941041f Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveClose_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveStatusBar9_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveStatusBar9_16x.png new file mode 100644 index 000000000..ea9c23f2a Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/SaveStatusBar9_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Save_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Save_16x.png new file mode 100644 index 000000000..cbc55c22e Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Save_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/StubProject.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/StubProject.png new file mode 100644 index 000000000..cd0ad20ad Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/StubProject.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Undo_16x.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Undo_16x.png new file mode 100644 index 000000000..3c563e72f Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/Undo_16x.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/algorithm.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/algorithm.png new file mode 100644 index 000000000..ab6b4c30f Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/algorithm.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/checklist_white_32.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/checklist_white_32.png new file mode 100644 index 000000000..e904220a7 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/checklist_white_32.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/clipboard.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/clipboard.png new file mode 100644 index 000000000..22a9c8ee4 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/clipboard.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/pp_project.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/pp_project.png new file mode 100644 index 000000000..e5eb56670 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/pp_project.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol.png new file mode 100644 index 000000000..16c9b0194 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol1.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol1.png new file mode 100644 index 000000000..ba64d6a89 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/redo-arrow-symbol1.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stop.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stop.png new file mode 100644 index 000000000..33e74f508 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stop.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_126.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_126.png new file mode 100644 index 000000000..a06e8e1c8 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_126.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_32.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_32.png new file mode 100644 index 000000000..4dc3d53e5 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_32.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_whit_32.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_whit_32.png new file mode 100644 index 000000000..875611302 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/stub_project_whit_32.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/test.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/test.png new file mode 100644 index 000000000..1d60a840f Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/test.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest.png new file mode 100644 index 000000000..fcc6a4fb7 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest_126.png b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest_126.png new file mode 100644 index 000000000..a49c21379 Binary files /dev/null and b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Images/unitTest_126.png differ diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/DefaultNotificationManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/DefaultNotificationManager.cs new file mode 100644 index 000000000..7121e6ce0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/DefaultNotificationManager.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; +using Tango.Scripting.IDE.Windows; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE.Notifications +{ + public class DefaultNotificationManager : INotificationManager + { + public Task ShowDialog(TViewModel viewModel, TView view) + where TViewModel : IDEDialogViewModel + where TView : FrameworkElement + { + TaskCompletionSource source = new TaskCompletionSource(); + + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + DialogWindow window = new DialogWindow(); + window.Title = viewModel.Title; + window.Content = view; + view.DataContext = viewModel; + window.WindowStartupLocation = WindowStartupLocation.CenterOwner; + window.Owner = Application.Current.MainWindow; + viewModel.Accepted += () => + { + window.Close(); + source.SetResult(viewModel); + }; + viewModel.Canceled += () => + { + window.Close(); + source.SetResult(viewModel); + }; + window.ShowDialog(); + })); + + return source.Task; + } + + public Task ShowDialog(TViewModel viewModel) where TViewModel : IDEDialogViewModel + { + var modelName = typeof(TViewModel).Name; + var viewName = modelName.Replace("VM", ""); + var viewType = typeof(TViewModel).Assembly.GetType(typeof(TViewModel).Namespace + "." + viewName); + var view = Activator.CreateInstance(viewType) as FrameworkElement; + return ShowDialog(viewModel, view); + } + + public Task ShowDialog() where TViewModel : IDEDialogViewModel + { + return ShowDialog(Activator.CreateInstance()); + } + + public Task ShowError(string title, string message) + { + throw new NotImplementedException(); + } + + public Task ShowInfo(string title, string message) + { + throw new NotImplementedException(); + } + + public ProgressNotificationHandler ShowProgress(string title, string message, bool canCancel = false) + { + throw new NotImplementedException(); + } + + public Task ShowQuestion(string title, string message) + { + throw new NotImplementedException(); + } + + public Task ShowSuccess(string title, string message) + { + throw new NotImplementedException(); + } + + public Task ShowWarning(string title, string message) + { + throw new NotImplementedException(); + } + + private Task ShowMessageBox(String title, String message, bool hasCancel, BitmapSource icon) + { + return null; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/INotificationManager.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/INotificationManager.cs new file mode 100644 index 000000000..1ed516b28 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/INotificationManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE.Notifications +{ + /// + /// Represents the IDE notification manager. + /// + public interface INotificationManager + { + /// + /// Displays the specified TView as a dialog and TViewModel as it's data context. + /// + /// The type of the view model. + /// The type of the view. + /// The view model. + /// The view. + /// + Task ShowDialog(TViewModel viewModel, TView view) where TViewModel : IDEDialogViewModel where TView : FrameworkElement; + + /// + /// Finds (by convention )the appropriate view by the specified TViewModel name. + /// The search pattern is ViewVM - VM + View. + /// + /// The type of the view model. + /// The view model. + /// + Task ShowDialog(TViewModel viewModel) where TViewModel : IDEDialogViewModel; + + /// + /// Finds (by convention )the appropriate view by the specified TViewModel name. + /// The search pattern is ViewVM - VM + View. + /// The view model instance will be created automatically and must contain a parameterless constructor. + /// + /// The type of the view model. + /// The view model. + /// + Task ShowDialog() where TViewModel : IDEDialogViewModel; + + /// + /// Displays an error message. + /// + /// The title. + /// The message. + /// + Task ShowError(String title, String message); + + /// + /// Displays an error message. + /// + /// The title. + /// The message. + /// + Task ShowInfo(String title, String message); + + /// + /// Displays a warning message. + /// + /// The title. + /// The message. + /// + Task ShowWarning(String title, String message); + + /// + /// Displays a positive message. + /// + /// The title. + /// The message. + /// + Task ShowSuccess(String title, String message); + + /// + /// Displays a question and returns the result. + /// + /// The title. + /// The message. + /// + Task ShowQuestion(String title, String message); + + /// + /// Displays an intermediate progress dialog and returns an instance of . + /// Once the progress notification handler will be disposed the dialog will close. + /// + /// The title. + /// The message. + /// if set to true the dialog will contain a cancel button. + /// + ProgressNotificationHandler ShowProgress(String title, String message, bool canCancel = false); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/ProgressNotificationHandler.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/ProgressNotificationHandler.cs new file mode 100644 index 000000000..b36419400 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Notifications/ProgressNotificationHandler.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.IDE.Notifications +{ + public class ProgressNotificationHandler : IDisposable + { + private Action _disposeAction; + + public ProgressNotificationHandler(Action disposeAction) + { + _disposeAction = disposeAction; + } + + public void Dispose() + { + _disposeAction?.Invoke(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Project.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Project.cs new file mode 100644 index 000000000..5a950d2c7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Project.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.SharedUI.Helpers; + +namespace Tango.Scripting.IDE +{ + public abstract class Project : IProject + { + private static Dictionary _imageCache; + + static Project() + { + _imageCache = new Dictionary(); + } + + public string FilePath { get; set; } + + public string WorkingFolder => Path.GetDirectoryName(FilePath); + + public string Name => Path.GetFileNameWithoutExtension(FilePath); + + public abstract BitmapSource Image { get; } + + public ObservableCollection Items { get; set; } + + public Project() + { + Items = new ObservableCollection(); + } + + public abstract Task Build(); + public abstract Task Run(); + + protected static BitmapSource GetImage(String name) + { + if (_imageCache.ContainsKey(name)) + { + return _imageCache[name]; + } + else + { + var image = ResourceHelper.GetImageFromResources(name); + _imageCache.Add(name, image); + return image; + } + } + + public bool CanOpen => false; + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItem.cs new file mode 100644 index 000000000..8adc26dc7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItem.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; +using Tango.Core; +using Tango.SharedUI.Helpers; + +namespace Tango.Scripting.IDE +{ + public abstract class ProjectItem : ExtendedObject, IProjectItem + { + private static Dictionary _imageCache; + + static ProjectItem() + { + _imageCache = new Dictionary(); + } + + public string Name { get; set; } + public ObservableCollection Items { get; set; } + + public ProjectItem() + { + Items = new ObservableCollection(); + } + + public abstract BitmapSource Image { get; } + + public abstract FrameworkElement OnGetView(); + + private FrameworkElement GetView() + { + return OnGetView(); + } + + protected static BitmapSource GetImage(String name) + { + if (_imageCache.ContainsKey(name)) + { + return _imageCache[name]; + } + else + { + var image = ResourceHelper.GetImageFromResources(name); + _imageCache.Add(name, image); + return image; + } + } + + public abstract bool CanOpen { get; } + public FrameworkElement View => GetView(); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/CSharpScriptItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/CSharpScriptItem.cs new file mode 100644 index 000000000..cf4811047 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/CSharpScriptItem.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; +using Tango.Scripting.IDE.ProjectItemsViews; + +namespace Tango.Scripting.IDE.ProjectItems +{ + public class CSharpScriptItem : ProjectItem + { + public override BitmapSource Image => GetImage("Images/CSharpScriptItem.png"); + + private String _code; + public String Code + { + get { return _code; } + set { _code = value; RaisePropertyChangedAuto(); } + } + + public override bool CanOpen => true; + + public override FrameworkElement OnGetView() + { + return new CSharpScriptItemView(this); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssembliesItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssembliesItem.cs new file mode 100644 index 000000000..0e66fd241 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssembliesItem.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE.ProjectItems +{ + public class ReferenceAssembliesItem : ProjectItem + { + public ReferenceAssembliesItem() + { + Name = "References"; + } + + public override BitmapSource Image => GetImage("Images/Reference.png"); + + public override FrameworkElement OnGetView() + { + return null; + } + + public override bool CanOpen => false; + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssemblyItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssemblyItem.cs new file mode 100644 index 000000000..e9cd1e8f2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItems/ReferenceAssemblyItem.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE.ProjectItems +{ + public class ReferenceAssemblyItem : ProjectItem + { + public String Path { get; set; } + public override BitmapSource Image => GetImage("Images/Reference.png"); + + public override FrameworkElement OnGetView() + { + return null; + } + + public override bool CanOpen => false; + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml new file mode 100644 index 000000000..682956205 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml @@ -0,0 +1,13 @@ + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml.cs new file mode 100644 index 000000000..34c604b83 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectItemsViews/CSharpScriptItemView.xaml.cs @@ -0,0 +1,29 @@ +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.Navigation; +using System.Windows.Shapes; +using Tango.Scripting.IDE.ProjectItems; + +namespace Tango.Scripting.IDE.ProjectItemsViews +{ + /// + /// Interaction logic for CSharpScriptItemView.xaml + /// + public partial class CSharpScriptItemView : UserControl + { + public CSharpScriptItemView(CSharpScriptItem scriptItem) + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectType.cs new file mode 100644 index 000000000..86efc4330 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectType.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.SharedUI.Helpers; + +namespace Tango.Scripting.IDE +{ + public abstract class ProjectType : IProjectType + { + private static Dictionary _imageCache; + + static ProjectType() + { + _imageCache = new Dictionary(); + } + + public abstract string Name { get; } + public abstract string Description { get; } + public abstract string Extention { get; } + public abstract BitmapSource SmallImage { get; } + public abstract BitmapSource LargeImage { get; } + + public static BitmapSource GetImage(String name) + { + if (_imageCache.ContainsKey(name)) + { + return _imageCache[name]; + } + else + { + var image = ResourceHelper.GetImageFromResources(name); + _imageCache.Add(name, image); + return image; + } + } + + public abstract IProject NewProject(string projectPath); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs new file mode 100644 index 000000000..6fe1316f4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; +using Tango.Scripting.IDE.ProjectItems; +using Tango.Scripting.IDE.Projects; + +namespace Tango.Scripting.IDE.ProjectTypes +{ + public class StubProjectType : ProjectType + { + public override IProject NewProject(string projectPath) + { + StubProject project = new StubProject(); + + project.FilePath = projectPath + Extention; ; + + var referenceAssembliesItem = new ReferenceAssembliesItem(); + + referenceAssembliesItem.Items.Add(new ReferenceAssemblyItem() + { + Path = "mscorelib.dll", + Name = "System.dll", + }); + referenceAssembliesItem.Items.Add(new ReferenceAssemblyItem() + { + Path = "System.Core.dll", + Name = "System.Core.dll", + }); + + project.Items.Add(referenceAssembliesItem); + + project.Items.Add(new CSharpScriptItem() { Name = "main.csx" }); + project.Items.Add(new CSharpScriptItem() { Name = "next.csx" }); + + return project; + } + + public override string Name => "Stub Project"; + public override string Description => "Create a stub project template."; + public override string Extention => ".stub"; + public override BitmapSource SmallImage => GetImage("Images/stub_project_32.png"); + public override BitmapSource LargeImage => GetImage("Images/stub_project_126.png"); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs new file mode 100644 index 000000000..42bab7059 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; +using Tango.Scripting.IDE.ProjectItems; +using Tango.Scripting.IDE.Projects; + +namespace Tango.Scripting.IDE.ProjectTypes +{ + public class UnitTestProjectType : ProjectType + { + public override IProject NewProject(string projectPath) + { + UnitTestProject project = new UnitTestProject(); + + project.FilePath = projectPath + Extention; ; + + var referenceAssembliesItem = new ReferenceAssembliesItem(); + + referenceAssembliesItem.Items.Add(new ReferenceAssemblyItem() + { + Path = "mscorelib.dll", + Name = "System.dll", + }); + referenceAssembliesItem.Items.Add(new ReferenceAssemblyItem() + { + Path = "System.Core.dll", + Name = "System.Core.dll", + }); + + project.Items.Add(referenceAssembliesItem); + + return project; + } + + public override string Name => "Unit Test Project"; + public override string Description => "Create a unit test project template."; + public override string Extention => ".unit"; + public override BitmapSource SmallImage => GetImage("Images/unitTest.png"); + public override BitmapSource LargeImage => GetImage("Images/unitTest_126.png"); + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/CSharpProject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/CSharpProject.cs new file mode 100644 index 000000000..62e830d06 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/CSharpProject.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.IDE.Projects +{ + public abstract class CSharpProject : Project + { + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/StubProject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/StubProject.cs new file mode 100644 index 000000000..bc915fa77 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/StubProject.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE.Projects +{ + public class StubProject : CSharpProject + { + public override BitmapSource Image => GetImage("Images/StubProject.png"); + + public override Task Build() + { + throw new NotImplementedException(); + } + + public override Task Run() + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/UnitTestProject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/UnitTestProject.cs new file mode 100644 index 000000000..e5510ad45 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Projects/UnitTestProject.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.Scripting.IDE.Projects +{ + public class UnitTestProject : CSharpProject + { + public override BitmapSource Image { get; } + + public override Task Build() + { + throw new NotImplementedException(); + } + + public override Task Run() + { + throw new NotImplementedException(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..073634992 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +[assembly: AssemblyTitle("Tango - Scripting IDE Library")] +[assembly: ComVisible(false)] +[assembly: AssemblyVersion("2.0.36.1608")] + + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.Designer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.Designer.cs new file mode 100644 index 000000000..ec92331cb --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tango.Scripting.IDE.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tango.Scripting.IDE.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.resx b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.Designer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.Designer.cs new file mode 100644 index 000000000..2a0fdc6a9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tango.Scripting.IDE.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.settings b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Resources.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Resources.xaml new file mode 100644 index 000000000..ef5d1b599 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Resources.xaml @@ -0,0 +1,127 @@ + + + #202020 + #252526 + #2D2D30 + #676767 + #007ACC + #007ACC + + + + + + + + + + + + #2D2D30 + #FFFFFFFF + #2D2D30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml new file mode 100644 index 000000000..587997450 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml.cs new file mode 100644 index 000000000..3c816be05 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView.xaml.cs @@ -0,0 +1,28 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE +{ + /// + /// Interaction logic for ScriptIDEControl.xaml + /// + public partial class ScriptIDEView : UserControl + { + public ScriptIDEView() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml new file mode 100644 index 000000000..67a673d3b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml @@ -0,0 +1,271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Debug + Release + Configuration Manager... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml.cs new file mode 100644 index 000000000..5e4509beb --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEView2.xaml.cs @@ -0,0 +1,93 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.Scripting.IDE +{ + + /// + /// Interaction logic for ScriptIDEView2.xaml + /// + public partial class ScriptIDEView2 : UserControl + { + public enum eSkin { Dark, Light } + public static eSkin Skin { get; set; } + public ScriptIDEView2() + { + InitializeComponent(); + } + public void ChangeSkin(eSkin newSkin) + { + Skin = newSkin; + Resources.Clear(); + Resources.MergedDictionaries.Clear(); + if (Skin == eSkin.Dark) + ApplyResources("Themes/DarkThemesColors.xaml"); + else if (Skin == eSkin.Light) + ApplyResources("Themes/LightThemesColors.xaml"); + ApplyResources("Themes/Shared.xaml"); + } + + private void ApplyResources(string src) + { + var dict = new ResourceDictionary() { Source = new Uri(src, UriKind.Relative) }; + foreach (var mergeDict in dict.MergedDictionaries) + { + Resources.MergedDictionaries.Add(mergeDict); + } + + foreach (var key in dict.Keys) + { + Resources[key] = dict[key]; + } + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + MessageBox.Show("I am here"); + } + private void TreeViewControl_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + + IProject SelectedItem = SolutionTree.SelectedItem as IProject; + if(SelectedItem != null && DataContext is ScriptIDEViewVM && ((ScriptIDEViewVM)DataContext).IsSolutionProject(SelectedItem)) + { + SolutionTree.ContextMenu = SolutionTree.Resources["SolutionContext"] as System.Windows.Controls.ContextMenu; + } + else + { + SolutionTree.ContextMenu = SolutionTree.Resources["FolderContext"] as System.Windows.Controls.ContextMenu; + } + } + + private void SolutionTree_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject); + + if (treeViewItem != null) + { + //treeViewItem.Focus(); + treeViewItem.IsSelected = true; + e.Handled = true; + } + } + static TreeViewItem VisualUpwardSearch(DependencyObject source) + { + while (source != null && !(source is TreeViewItem)) + source = VisualTreeHelper.GetParent(source); + + return source as TreeViewItem; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs new file mode 100644 index 000000000..facebe061 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Imaging; +using Tango.Core.Commands; +using Tango.Scripting.IDE.Controls; +using Tango.Scripting.IDE.Dialogs; +using Tango.Scripting.IDE.Notifications; +using Tango.Scripting.IDE.Projects; +using Tango.Scripting.IDE.ProjectTypes; +using Tango.SharedUI; + +namespace Tango.Scripting.IDE +{ + public class ScriptIDEViewVM : IDEViewModel + { + private List _projectTypes; + public ObservableCollection ErrorList { get; set; } + + #region Properties + + private Solution _solution; + public Solution Solution + { + get { return _solution; } + set { _solution = value; RaisePropertyChangedAuto(); } + } + + private IProject _selectedProject; + public IProject SelectedProject + { + get { return _selectedProject; } + set { _selectedProject = value; RaisePropertyChangedAuto(); } + } + + private IProjectItem _selectedProjectItem; + public IProjectItem SelectedProjectItem + { + get { return _selectedProjectItem; } + set { _selectedProjectItem = value; RaisePropertyChangedAuto(); } + } + + private ObservableCollection _openProjectItems; + public ObservableCollection OpenProjectItems + { + get { return _openProjectItems; } + set { _openProjectItems = value; RaisePropertyChangedAuto(); } + } + private bool _isRunProject = false; + public bool IsRunProject + { + get { return _isRunProject; } + set + { + if (_isRunProject != value) + { + _isRunProject = value; RaisePropertyChangedAuto(); + } + } + } + #endregion + + #region Commands + public RelayCommand NewProjectCommand { get; set; } + public RelayCommand AddProjectCommand { get; set; } + public RelayCommand OpenProjectItemCommand { get; set; } + + public RelayCommand CloseProjectItemCommand { get; set; } + public RelayCommand RunProject { get; set; } + public RelayCommand StopProject { get; set; } + + #endregion + + #region Constructors + + public ScriptIDEViewVM() : base() + { + _projectTypes = new List(); + OpenProjectItems = new ObservableCollection(); + ErrorList = new ObservableCollection(); + + RegisterProjectType(new StubProjectType()); + RegisterProjectType(new UnitTestProjectType()); + + Solution = new Solution(); + Solution.SolutionLocation = @"C:\Test"; + Solution.Projects.Add(_projectTypes.First().NewProject("Test Project.stub")); + + //Init Commands + NewProjectCommand = new RelayCommand(AddNewProject); + AddProjectCommand = new RelayCommand(AddProject); + OpenProjectItemCommand = new RelayCommand(OpenProjectItem); + CloseProjectItemCommand = new RelayCommand(CloseProjectItem); + RunProject = new RelayCommand(RunProjectCommand); + StopProject = new RelayCommand(StopRunProjectCommand); + + NotificationManager = new DefaultNotificationManager(); + } + + private void RunProjectCommand(object obj) + { + // MessageBox.Show("You said: RunProjectCommand"); + } + private void StopRunProjectCommand(object obj) + { + IsRunProject = false; + } + + #endregion + + #region Public Methods + + private void OpenProjectItem(IProjectItem projectItem) + { + if (!OpenProjectItems.Contains(projectItem)) + { + OpenProjectItems.Add(projectItem); + } + + SelectedProjectItem = projectItem; + } + + private void CloseProjectItem(IProjectItem projectItem) + { + OpenProjectItems.Remove(projectItem); + + SelectedProjectItem = OpenProjectItems.FirstOrDefault(); + } + + public void RegisterProjectType(IProjectType projectType) + { + _projectTypes.Add(projectType); + } + + public void UnRegisterProjectItemHandler(IProjectType projectType) + { + _projectTypes.Remove(projectType); + } + /// + /// Open a dialog to create a new project + /// + private async void AddNewProject() + { + var vm = await NotificationManager.ShowDialog(new NewProjectViewVM(Settings.LastSolutionLocations)); + + if (vm.DialogResult) + { + Solution newSolution = new Solution(); + newSolution.Name = vm.SolutionName; + newSolution.SolutionLocation = vm.ProjectLocation; + Solution = newSolution; + StringBuilder builder = new StringBuilder(vm.ProjectLocation); + builder.AppendFormat(@"\{0}", vm.ProjectName); + Solution.Projects.Add(vm.SelectedProjectType.NewProject(builder.ToString())); + + Settings.LastSolutionLocations.Insert(0, vm.ProjectLocation); + Settings.Save(); + } + } + private async void AddProject() + { + var vm = await NotificationManager.ShowDialog(new AddProjectViewVM() + { + ProjectLocation = Solution.SolutionLocation + //ProjectLocation = Path.GetDirectoryName(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase)) + //ProjectLocation = "Current Solution folder..." + }); + + if (vm.DialogResult) + { + StringBuilder builder = new StringBuilder(vm.ProjectLocation); + builder.AppendFormat(@"\{0}", vm.ProjectName); + Solution.Projects.Add(vm.SelectedProjectType.NewProject(builder.ToString())); + } + } + public bool IsSolutionProject(IProject SelectedItem) + { + if (SelectedItem is StubProject) + return true; + return false; + + } + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Solution.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Solution.cs new file mode 100644 index 000000000..cd7806698 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Solution.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.IDE +{ + public class Solution + { + public ObservableCollection Projects { get; set; } + public string Name{get; set;} + public string SolutionLocation { get; set; } + + public Solution() + { + Projects = new ObservableCollection(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj new file mode 100644 index 000000000..9fac7041d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj @@ -0,0 +1,308 @@ + + + + + Debug + AnyCPU + {C9F60285-91FB-4293-BCF5-164D76755CDD} + library + Tango.Scripting.IDE + Tango.Scripting.IDE + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + true + full + false + ..\..\Build\Scripting\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\Build\Scripting\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll + + + ..\..\packages\MahApps.Metro.1.5.0\lib\net45\MahApps.Metro.dll + + + ..\..\packages\Microsoft.WindowsAPICodePack-Core.1.1.0.0\lib\Microsoft.WindowsAPICodePack.dll + + + ..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.Shell.dll + + + ..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll + + + + + + + + ..\..\packages\MahApps.Metro.1.5.0\lib\net45\System.Windows.Interactivity.dll + + + + + + + + + 4.0 + + + + + + + + GlobalVersionInfo.cs + + + + + + + + NewProjectView.xaml + + + AddProjectView.xaml + + + + + + + + + + + + + + + CSharpScriptItemView.xaml + + + + + + + + + + + + + + ScriptIDEView.xaml + + + ScriptIDEView2.xaml + + + + + + + DialogWindow.xaml + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {d8f1ad85-526a-4f50-b6dc-d437af63d8d8} + Tango.Settings + + + {8491d07b-c1f6-4b62-a412-41b9fd2d6538} + Tango.SharedUI + + + {da62fa39-668b-47a6-b0f2-d2c1daf777b0} + Tango.Scripting.Editors + + + {1e938fd2-c669-4738-98c9-77f96ce4d451} + Tango.Scripting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ButtonStyle.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ButtonStyle.xaml new file mode 100644 index 000000000..f30adc85d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ButtonStyle.xaml @@ -0,0 +1,81 @@ + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ComboboxStyle.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ComboboxStyle.xaml new file mode 100644 index 000000000..503f8c20c --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ComboboxStyle.xaml @@ -0,0 +1,200 @@ + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DarkThemesColors.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DarkThemesColors.xaml new file mode 100644 index 000000000..caab88ef1 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DarkThemesColors.xaml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DataGridStyle.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DataGridStyle.xaml new file mode 100644 index 000000000..5ec4c7ff4 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/DataGridStyle.xaml @@ -0,0 +1,248 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Generic.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Generic.xaml new file mode 100644 index 000000000..6c016ae84 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Generic.xaml @@ -0,0 +1,36 @@ + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/LightThemesColors.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/LightThemesColors.xaml new file mode 100644 index 000000000..da65cc9b7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/LightThemesColors.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/MenuDict.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/MenuDict.xaml new file mode 100644 index 000000000..f51a50b50 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/MenuDict.xaml @@ -0,0 +1,229 @@ + + + + + + + + M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ScrollViewerStyle.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ScrollViewerStyle.xaml new file mode 100644 index 000000000..56c047424 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ScrollViewerStyle.xaml @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Shared.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Shared.xaml new file mode 100644 index 000000000..9b196411b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/Shared.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TabConrolStyle.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TabConrolStyle.xaml new file mode 100644 index 000000000..81b4463f6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TabConrolStyle.xaml @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ToolbarStyle.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ToolbarStyle.xaml new file mode 100644 index 000000000..6223934b5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/ToolbarStyle.xaml @@ -0,0 +1,236 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TreeViewItem.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TreeViewItem.xaml new file mode 100644 index 000000000..f76c133d6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Themes/TreeViewItem.xaml @@ -0,0 +1,177 @@ + + + + M0,0L0,6 6,0z + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ViewModelLocator.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ViewModelLocator.cs new file mode 100644 index 000000000..ffe78f49d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/ViewModelLocator.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core.DI; +using Tango.Scripting.IDE.Dialogs; +using Tango.Scripting.IDE.Notifications; + +namespace Tango.Scripting.IDE +{ + /// + /// This class contains static references to all the view models in the + /// application and provides an entry point for the bindings. + /// + public static class ViewModelLocator + { + /// + /// Initializes a new instance of the ViewModelLocator class. + /// + static ViewModelLocator() + { + TangoIOC.Default.Register(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml new file mode 100644 index 000000000..683391afd --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml.cs new file mode 100644 index 000000000..cfddd09e3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml.cs @@ -0,0 +1,28 @@ +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.Scripting.IDE.Windows +{ + /// + /// Interaction logic for DialogWindow.xaml + /// + public partial class DialogWindow : MetroWindow + { + public DialogWindow() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/app.config b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/app.config new file mode 100644 index 000000000..877a49c00 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/app.config @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/packages.config b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/packages.config new file mode 100644 index 000000000..224f74b3a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.IDE/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs new file mode 100644 index 000000000..03c96a413 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class CompilationError + { + public String File { get; set; } + + public String Name + { + get { return Path.GetFileName(File); } + } + + public String Message { get; set; } + public int Line { get; set; } + public int Character { get; set; } + + public override string ToString() + { + return String.Format("{0} {1} ({2},{3})", Message, Name, Line, Character); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs new file mode 100644 index 000000000..1b27dfe5b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class CompilationException : Exception + { + public List Errors { get; set; } + + public CompilationException() + { + Errors = new List(); + } + + public CompilationException(params CompilationError[] errors) + { + Errors = errors.ToList(); + } + + public override string ToString() + { + return String.Join(Environment.NewLine, Errors.Select(x => x.ToString())); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs new file mode 100644 index 000000000..ea29986b7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + internal static class ExtensionMethods + { + public static List ToLines(this String str) + { + //return str.Split(new[] { '\r', '\n' }).ToList(); + if (str == null) return new List(); + return str.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None).ToList(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs new file mode 100644 index 000000000..4cac65ef6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class GlobalObject : IDisposable + { + public GlobalObject() + { + + } + + public virtual void Dispose() + { + + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs new file mode 100644 index 000000000..3062d6bc0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public interface IScriptSession : IDisposable + { + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs new file mode 100644 index 000000000..84d4d8d04 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public interface IScriptingEngine : IDisposable + { + Task Compile(Script script); + Task Run(Script script); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs new file mode 100644 index 000000000..15760c950 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs @@ -0,0 +1,382 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Tango.Scripting.Parsing +{ + public class ScriptParser + { + private const string fakeScript = "CurrentScript"; + private string fakeScriptName = "CurrentScript."; + + private String ReplaceFakeScript(String str) + { + if (str != null) + { + return str.Replace(fakeScriptName, "").Replace(fakeScript, ""); + } + + return str; + } + + private CompilationUnitSyntax GetRoot(String code) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + var root = (CompilationUnitSyntax)tree.GetRoot(); + return root; + } + + private SemanticModel GetSemanticModel(String code) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + var compilation = CSharpCompilation.Create("CSharpScript").AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + return model; + } + + public List GetContextSymbols(String code, int caretOffset) + { + var currentNode = GetCaretOffsetNode(code, caretOffset); + + if (currentNode == null) return new List(); + + if (currentNode.Ancestors().OfType().Count() == 0) + { + var usings = GetUsingsFull(code); + + int removedLength = usings.Select(x => x.Length).Sum(); + + foreach (var use in usings) + { + code = code.Replace(use, ""); + } + + String scriptStart = $"public class {fakeScript}\n{{\n"; + code = $"{scriptStart}{code}\n}}"; + caretOffset = caretOffset - removedLength + scriptStart.Length; + } + + var model = GetSemanticModel(code); + var symbols = model.LookupSymbols(caretOffset).ToList().Where(x => x.Kind == SymbolKind.Property || x.Kind == SymbolKind.Field || x.Kind == SymbolKind.Parameter || x.Kind == SymbolKind.Local || x.Kind == SymbolKind.Method).ToList(); + + List vars = new List(); + + foreach (var symbol in symbols.DistinctBy(x => x.Name)) + { + if (symbol.ContainingSymbol.GetType().Name == "LambdaSymbol") + { + var invocationNode = currentNode.Ancestors().OfType().FirstOrDefault(); + + if (invocationNode != null) + { + var expressionNode = invocationNode.Expression as MemberAccessExpressionSyntax; + + if (expressionNode != null && expressionNode.Name != null) + { + var name = expressionNode.Name as GenericNameSyntax; + + if (name != null) + { + var type = name.TypeArgumentList.Arguments.FirstOrDefault()?.ToString(); + + vars.Add(new ScriptSymbol() + { + Name = symbol.Name, + Type = ReplaceFakeScript(type), + Class = ReplaceFakeScript(symbol.ContainingType?.Name), + Kind = symbol.Kind, + Accessibility = symbol.DeclaredAccessibility, + ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name), + Summary = GetSymbolDocumentation(symbol), + }); + } + } + } + } + if (symbol.Kind == SymbolKind.Method) + { + var prop = symbol.GetType().GetProperty("ReturnType"); + + if (prop != null) + { + ScriptSymbol m = new ScriptSymbol() + { + Name = symbol.Name, + Type = ReplaceFakeScript(prop.GetValue(symbol).ToString()), + Class = ReplaceFakeScript(symbol.ContainingType?.Name), + Kind = symbol.Kind, + Accessibility = symbol.DeclaredAccessibility, + ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name), + Summary = GetSymbolDocumentation(symbol), + }; + + m.Parameters = GetMethodSymbolParameters(symbol); + + vars.Add(m); + } + } + else + { + var prop = symbol.GetType().GetProperty("Type"); + + if (prop != null) + { + vars.Add(new ScriptSymbol() + { + Name = symbol.Name, + Type = ReplaceFakeScript(prop.GetValue(symbol).ToString()), + Class = ReplaceFakeScript(symbol.ContainingType?.Name), + Kind = symbol.Kind, + Accessibility = symbol.DeclaredAccessibility, + ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name), + Summary = GetSymbolDocumentation(symbol), + }); + } + } + } + + return vars.Where(x => x.Type != "?").ToList(); + } + + public List GetUsings(String code) + { + Regex r = new Regex("(using [^;^\n]+;)"); + var matches = r.Matches(code); + return matches.OfType().Select(x => x.Value.Replace("using ", "").Replace(";", "").Replace("\n", "").Replace("\t", "").Replace("\r", "")).ToList(); + } + + public List GetUsingsFull(String code) + { + Regex r = new Regex("(using [^;^\n]+;)"); + var matches = r.Matches(code); + return matches.OfType().Select(x => x.Value).ToList(); + } + + public List GetDeclaredTypes(String code) + { + List types = new List(); + + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + var compilation = CSharpCompilation.Create("CSharpScript").AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + foreach (var d in root.DescendantNodes().Where(x => x is ClassDeclarationSyntax || x is EnumDeclarationSyntax || x is InterfaceDeclarationSyntax).OfType()) + { + var type = model.GetDeclaredSymbol(d); + if (!String.IsNullOrWhiteSpace(type.Name)) + { + ScriptType scriptType = new ScriptType(); + scriptType.Name = type.Name; + scriptType.Kind = type.TypeKind; + scriptType.ContainingNamespace = type.ContainingNamespace?.Name; + scriptType.Summary = GetNodeDocumentation(d); + + + foreach (var symbol in d.DescendantNodes().OfType()) + { + var symbolModel = model.GetDeclaredSymbol(symbol); + + scriptType.Symbols.Add(new ScriptSymbol() + { + Class = scriptType.Name, + Accessibility = symbolModel.DeclaredAccessibility, + Kind = SymbolKind.Property, + Name = symbolModel.Name, + Type = symbolModel.Type.ToString(), + ContainingNamespace = symbolModel.ContainingNamespace?.Name, + Summary = GetNodeDocumentation(symbol), + }); + } + + foreach (var symbol in d.DescendantNodes().OfType()) + { + var symbolModel = model.GetDeclaredSymbol(symbol.Declaration.Variables.FirstOrDefault()) as IFieldSymbol; + + if (symbolModel != null) + { + scriptType.Symbols.Add(new ScriptSymbol() + { + Class = scriptType.Name, + Accessibility = symbolModel.DeclaredAccessibility, + Kind = SymbolKind.Field, + Name = symbolModel.Name, + Type = symbolModel.Type.ToString(), + ContainingNamespace = symbolModel.ContainingNamespace?.Name, + Summary = GetNodeDocumentation(symbol), + }); + } + } + + foreach (var symbol in d.DescendantNodes().OfType()) + { + var symbolModel = model.GetDeclaredSymbol(symbol); + + if (symbolModel != null) + { + ScriptSymbol m = new ScriptSymbol() + { + Class = scriptType.Name, + Accessibility = symbolModel.DeclaredAccessibility, + Kind = SymbolKind.Method, + Name = symbolModel.Name, + Type = symbolModel.ReturnType.ToString(), + ContainingNamespace = symbolModel.ContainingNamespace?.Name, + Summary = GetNodeDocumentation(symbol), + }; + + foreach (var p in symbol.DescendantNodes().OfType()) + { + if (p.Type != null && p.Identifier != null) + { + m.Parameters.Add(new KeyValuePair(p.Type.ToString(), p.Identifier.ToString())); + } + } + + scriptType.Symbols.Add(m); + } + } + + types.Add(scriptType); + } + } + + return types; + } + + public ObjectCreationExpressionSyntax GetCurrentConstructionExpression(String code) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().OfType().FirstOrDefault(); + } + + public T GetExpressionFirst(String line) where T : CSharpSyntaxNode + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(line); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().OfType().FirstOrDefault(); + } + + public List GetExpressions(String line) where T : CSharpSyntaxNode + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(line); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().OfType().ToList(); + } + + public List GetDirectExpressions(SyntaxNode node) where T : CSharpSyntaxNode + { + return node.DescendantNodes().OfType().ToList(); + } + + private SyntaxNode GetCaretOffsetNode(String code, int offset) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().Where(x => offset >= x.FullSpan.Start && offset <= x.FullSpan.End).OrderBy(x => x.FullSpan.Length).FirstOrDefault(); + } + + private List GetNodeAncestors(SyntaxNode node) + { + return node.Ancestors().ToList(); + } + + private String GetNodeDocumentation(SyntaxNode node) + { + try + { + var trivia = node.GetLeadingTrivia().FirstOrDefault(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia); + + if (trivia != null && !String.IsNullOrWhiteSpace(trivia.ToString())) + { + return trivia.ToString().Replace("//", ""); + } + } + catch { } + + return "No documentation."; + } + + private String GetSymbolDocumentation(ISymbol symbol) + { + if (symbol != null) + { + var prop = symbol.GetType().GetProperty("SyntaxNode"); + + if (prop != null) + { + var node = prop.GetValue(symbol) as SyntaxNode; + + if (node != null) + { + return GetNodeDocumentation(node.Parent); + } + } + } + + return "No documentation."; + } + + private SyntaxNode GetSymbolSyntaxNode(ISymbol symbol) + { + if (symbol != null) + { + var prop = symbol.GetType().GetProperty("SyntaxNode"); + + if (prop != null) + { + var node = prop.GetValue(symbol) as SyntaxNode; + + return node; + } + } + + return null; + } + + private List> GetMethodSymbolParameters(ISymbol symbol) + { + List> parameters = new List>(); + + try + { + var prop = symbol.GetType().GetProperty("Parameters"); + + if (prop != null) + { + var array = prop.GetValue(symbol) as IEnumerable; + + foreach (var item in array) + { + var type = item.GetType().GetProperty("Type").GetValue(item).ToString(); + var value = item.GetType().GetProperty("Name").GetValue(item).ToString(); + + parameters.Add(new KeyValuePair(type, value)); + } + } + } + catch { } + + return parameters; + } + + public String IndentCSharpCode(String code) + { + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot().NormalizeWhitespace(); + var ret = root.ToFullString(); + return ret; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs new file mode 100644 index 000000000..d6fdaeebf --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs @@ -0,0 +1,31 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Parsing +{ + public class ScriptSymbol + { + public String Name { get; set; } + public String Type { get; set; } + public SymbolKind Kind { get; set; } + public String Class { get; set; } + public Accessibility Accessibility { get; set; } + public String ContainingNamespace { get; set; } + public List> Parameters { get; set; } + public String Summary { get; set; } + + public ScriptSymbol() + { + Parameters = new List>(); + } + + public override string ToString() + { + return $"{Kind.ToString()} : {Type} : {Name}"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs new file mode 100644 index 000000000..3ca34a85e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs @@ -0,0 +1,23 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Parsing +{ + public class ScriptType + { + public String Name { get; set; } + public TypeKind Kind { get; set; } + public List Symbols { get; set; } + public string ContainingNamespace { get; set; } + public String Summary { get; set; } + + public ScriptType() + { + Symbols = new List(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1fd19cced --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tango - Scripting Library")] +[assembly: ComVisible(false)] +[assembly: AssemblyVersion("2.0.36.1608")] diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs new file mode 100644 index 000000000..c4e1925a2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class ReferenceAssembly + { + public Assembly Assembly { get; set; } + + public String File + { + get { return Assembly.Location; } + } + + public String Name + { + get { return Path.GetFileNameWithoutExtension(File); } + } + + public ReferenceAssembly(Assembly assembly) + { + Assembly = assembly; + } + + public ReferenceAssembly(String file) + { + Assembly = Assembly.LoadFrom(file); + } + + public ReferenceAssembly(Type type) + { + Assembly = type.Assembly; + } + + public override string ToString() + { + return Assembly.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs new file mode 100644 index 000000000..a4cd38c3d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class Script + { + public String Name { get; set; } + + public String File { get; set; } + + public GlobalObject GlobalObject { get; set; } + + public List ReferenceAssemblies { get; private set; } + + public List Imports { get; private set; } + + public String Code { get; set; } + + public String WorkingFolder { get; set; } + + public String EntryPoint { get; set; } + + public ApartmentState ApartmentState { get; set; } + + public Script() + { + ReferenceAssemblies = new List(); + Imports = new List(); + GlobalObject = new GlobalObject(); + File = "Script.cs"; + Name = "Script.cs"; + WorkingFolder = Environment.CurrentDirectory; + ApartmentState = ApartmentState.MTA; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs new file mode 100644 index 000000000..647ec7d87 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class ScriptSession + { + private Action _abortAction; + + public event EventHandler StateChanged; + + public Script Script { get; private set; } + + public String EffectiveCode { get; set; } + + public ScriptSessionState State { get; set; } + + public ScriptSession(Script script, String effectiveCode, Action abortAction) + { + _abortAction = abortAction; + Script = script; + EffectiveCode = effectiveCode; + } + + public void Abort() + { + _abortAction(); + State = ScriptSessionState.Aborted; + RaiseStateChanged(); + } + + internal void Failed(Exception ex) + { + State = ScriptSessionState.Failed; + RaiseStateChanged(null, ex); + } + + internal void Completed(object returnValue) + { + State = ScriptSessionState.Completed; + RaiseStateChanged(returnValue, null); + } + + private void RaiseStateChanged(object returnValue = null, Exception ex = null) + { + StateChanged?.Invoke(this, + new ScriptSessionStateChangedEventArgs() + { + ReturnValue = returnValue, + State = State, + Exception = ex + }); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs new file mode 100644 index 000000000..5b65623f9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public enum ScriptSessionState + { + Running, + Completed, + Aborted, + Failed, + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs new file mode 100644 index 000000000..7ea3b924b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class ScriptSessionStateChangedEventArgs : EventArgs + { + public Object ReturnValue { get; set; } + + public ScriptSessionState State { get; set; } + + public Exception Exception { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs new file mode 100644 index 000000000..b3f5348f8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs @@ -0,0 +1,268 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Tango.Scripting +{ + public class ScriptingEngine : IScriptingEngine + { + private class IncludeResult + { + public String File { get; set; } + public List Lines { get; set; } + } + + public Task Compile(Script script) + { + return Task.Factory.StartNew(() => + { + String code = script.Code; + List includeResults = new List(); + + try + { + includeResults.Add(new IncludeResult() { File = script.File, Lines = script.Code.ToLines() }); + code = ReplaceIncludes(script, code, script.WorkingFolder, includeResults); + } + catch (Exception ex) + { + throw new CompilationException(new CompilationError() { Message = ex.Message }); + } + + var options = CreateOptions(script); + + var csharpScript = CSharpScript.Create(code, options: options, globalsType: script.GlobalObject.GetType()); + + var results = csharpScript.Compile(); + + List errors = new List(); + + foreach (var result in results.Where(x => x.Severity == DiagnosticSeverity.Error)) + { + CompilationError error = new CompilationError(); + error.Message = result.GetMessage(); + error.Character = result.Location.GetMappedLineSpan().StartLinePosition.Character + 1; + + int lineIndex = 0; + IncludeResult include = null; + + foreach (var inc in includeResults) + { + for (int i = 0; i < inc.Lines.Count; i++) + { + if (inc.Lines[i] == code.ToLines()[result.Location.GetMappedLineSpan().StartLinePosition.Line]) + { + include = inc; + lineIndex = i; + break; + } + } + } + + if (include != null) + { + error.File = include.File; + error.Line = lineIndex + 1; + } + + errors.Add(error); + } + + if (errors.Count > 0) + { + throw new CompilationException(errors.ToArray()); + } + + return code; + }); + } + + public Task Run(Script script) + { + return Task.Factory.StartNew(() => + { + var effectivCode = Compile(script).Result; + + var options = CreateOptions(script); + + var _cancaller = new CancellationTokenSource(); + + Thread scriptThread = null; + ScriptSession session = null; + + session = new ScriptSession(script, effectivCode, () => + { + scriptThread.Abort(); + }); + + scriptThread = new Thread(() => + { + try + { + var result = CSharpScript.RunAsync(effectivCode, options: options, globals: script.GlobalObject, cancellationToken: _cancaller.Token).Result; + session.Completed(result.ReturnValue); + } + catch (ThreadAbortException) + { + + } + catch (Exception ex) + { + session.Failed(ex.InnerException); + } + }); + + scriptThread.SetApartmentState(script.ApartmentState); + scriptThread.IsBackground = true; + scriptThread.Start(); + + return session; + }); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + #region Private Methods + + private List GetIncludes(String code, String workingFolder) + { + if (Directory.Exists(workingFolder)) + { + Environment.CurrentDirectory = workingFolder; + } + + List lines = code.ToLines(); + List includeFiles = new List(); + + for (int i = 0; i < lines.Count; i++) + { + var line = lines[i]; + + if (line.Trim().StartsWith("include")) + { + String path = line.Replace("include", "").Trim().Replace("\"", ""); + + if (!File.Exists(path)) + { + throw new FileNotFoundException("Could not locate include file '" + path + "'."); + } + + includeFiles.Add(path); + } + } + + return includeFiles; + } + + private String ReplaceIncludes(Script script, String code, String workingFolder, List includeResults) + { + if (Directory.Exists(workingFolder)) + { + Environment.CurrentDirectory = workingFolder; + } + + List lines = code.ToLines().ToList(); + + for (int i = 0; i < lines.Count; i++) + { + var line = lines[i]; + + if (line.Trim().StartsWith("include")) + { + String path = line.Replace("include", "").Trim().Replace("\"", ""); + + if (!File.Exists(path)) + { + throw new FileNotFoundException("Could not locate include file '" + path + "'."); + } + + String includeContent = File.ReadAllText(path); + + includeResults.Add(new IncludeResult() { File = path, Lines = includeContent.ToLines() }); + + String content = ReplaceIncludes(script, includeContent, Path.GetDirectoryName(path), includeResults); + + if (!String.IsNullOrWhiteSpace(script.EntryPoint)) + { + if (content.Contains(script.EntryPoint + "(")) + { + throw new InvalidProgramException(String.Format("Include file '{0}' contains an OnExecute method. Please remove it before trying to compile.", path)); + } + } + + lines[i] = content; + } + } + + code = ClearUsings(String.Join(Environment.NewLine, lines)); + + return code; + } + + private String ClearUsings(String code) + { + List usings = new List(); + + List lines = code.ToLines(); + + foreach (var line in lines) + { + if (line.Trim().StartsWith("using")) + { + usings.Add(line); + } + } + + lines.RemoveAll(x => x.Trim().StartsWith("using")); + + return String.Join(Environment.NewLine, usings.Distinct()) + Environment.NewLine + String.Join(Environment.NewLine, lines); + } + + private ScriptOptions CreateOptions(Script script) + { + //My References. + var options = ScriptOptions.Default; + + //My Assemblies. + options = options.AddReferences(typeof(Form).Assembly.Location); + options = options.AddReferences(typeof(Enumerable).Assembly.Location); + options = options.AddReferences(typeof(ScriptingEngine).Assembly.Location); + + foreach (var asm in script.ReferenceAssemblies) + { + options = options.AddReferences(asm.File); + } + + //Imports. + options = options.AddImports( + "System", + "System.Collections.Generic", + "System.Linq", + "System.Text", + "System.Diagnostics", + "System.Windows.Forms", + "System.Threading" + ); + + if (script.Imports.Count > 0) + { + options = options.AddImports(script.Imports); + } + + return options; + } + + #endregion + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj new file mode 100644 index 000000000..044ac88d6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj @@ -0,0 +1,154 @@ + + + + + Debug + AnyCPU + {1E938FD2-C669-4738-98C9-77F96CE4D451} + Library + Properties + Tango.Scripting + Tango.Scripting + v4.6.1 + 512 + true + + + true + full + false + ..\..\Build\Scripting\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\Build\Scripting\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + ..\..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + + + + + + + + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + + + + + GlobalVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/app.config b/Software/Visual_Studio/Scripting/Tango.Scripting/app.config new file mode 100644 index 000000000..38ef71542 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/app.config @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/packages.config b/Software/Visual_Studio/Scripting/Tango.Scripting/packages.config new file mode 100644 index 000000000..02b4f06f6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/packages.config @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/TestApp/App.config b/Software/Visual_Studio/Scripting/TestApp/App.config new file mode 100644 index 000000000..b7f531317 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/App.config @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/TestApp/App.xaml b/Software/Visual_Studio/Scripting/TestApp/App.xaml new file mode 100644 index 000000000..68dd93e92 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Software/Visual_Studio/Scripting/TestApp/App.xaml.cs b/Software/Visual_Studio/Scripting/TestApp/App.xaml.cs new file mode 100644 index 000000000..f70c27428 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace TestApp +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml b/Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml new file mode 100644 index 000000000..56fe9da3c --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml.cs b/Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml.cs new file mode 100644 index 000000000..8ae84b20e --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/MainWindow.xaml.cs @@ -0,0 +1,77 @@ +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.Navigation; +using System.Windows.Shapes; +using Tango.Scripting; + +namespace TestApp +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private IScriptingEngine _engine; + private ScriptSession _session; + + public MainWindow() + { + InitializeComponent(); + + _engine = new ScriptingEngine(); + } + + private async void Button_Click(object sender, RoutedEventArgs e) + { + Script s = new Script(); + s.Code = +@" +using System; +using System.Windows.Forms; + +MessageBox.Show(""Hi Roy""); +"; + + try + { + _session = await _engine.Run(s); + _session.StateChanged += Session_StateChanged; + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString()); + } + } + + private void Session_StateChanged(object sender, ScriptSessionStateChangedEventArgs e) + { + if (e.State == ScriptSessionState.Completed) + { + MessageBox.Show(e.ReturnValue.ToString()); + } + else if (e.State == ScriptSessionState.Aborted) + { + MessageBox.Show("Aborted"); + } + else if (e.State == ScriptSessionState.Failed) + { + MessageBox.Show(e.Exception.ToString()); + } + } + + private void Button_Click_1(object sender, RoutedEventArgs e) + { + _session.Abort(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/TestApp/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Scripting/TestApp/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d2b37f2f3 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestApp")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Visual_Studio/Scripting/TestApp/Properties/Resources.Designer.cs b/Software/Visual_Studio/Scripting/TestApp/Properties/Resources.Designer.cs new file mode 100644 index 000000000..08bc18064 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TestApp.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestApp.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/TestApp/Properties/Resources.resx b/Software/Visual_Studio/Scripting/TestApp/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/TestApp/Properties/Settings.Designer.cs b/Software/Visual_Studio/Scripting/TestApp/Properties/Settings.Designer.cs new file mode 100644 index 000000000..361ead0b9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TestApp.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/TestApp/Properties/Settings.settings b/Software/Visual_Studio/Scripting/TestApp/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/TestApp/TestApp.csproj b/Software/Visual_Studio/Scripting/TestApp/TestApp.csproj new file mode 100644 index 000000000..ebcb2f004 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/TestApp.csproj @@ -0,0 +1,122 @@ + + + + + Debug + AnyCPU + {EB6A2B6A-5115-46F5-A969-DFBBB12FCCC1} + WinExe + TestApp + TestApp + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll + + + ..\packages\MahApps.Metro.1.5.0\lib\net45\MahApps.Metro.dll + + + + + ..\packages\MahApps.Metro.1.5.0\lib\net45\System.Windows.Interactivity.dll + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {6c55b776-26d4-4db3-a6ab-87e783b2f3d1} + Tango.Scripting.Editors + + + {c9f60285-91fb-4293-bcf5-164d76755cdd} + Tango.Scripting.IDE + + + {1e938fd2-c669-4738-98c9-77f96ce4d451} + Tango.Scripting + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/TestApp/packages.config b/Software/Visual_Studio/Scripting/TestApp/packages.config new file mode 100644 index 000000000..893958c68 --- /dev/null +++ b/Software/Visual_Studio/Scripting/TestApp/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs index e63e0bdd1..b2b2bc197 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionConfig.cs @@ -19,6 +19,7 @@ namespace Tango.TCC.BL public double SimilarityTolerance { get; set; } public CardDetectionHistogramMethods HistogramMethod { get; set; } public bool EnableDoubleChecking { get; set; } + public bool EnforceBarcodeDetection { get; set; } public CardDetectionConfig() { diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs index 50bcceeba..221659868 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs @@ -66,6 +66,7 @@ namespace Tango.TCC.BL SimilarityTolerance = config.SimilarityTolerance, HistogramMethod = (int)config.HistogramMethod, EnableDoubleChecking = config.EnableDoubleChecking, + EnforceBarcodeDetection = config.EnforceBarcodeDetection, }); detectionResult.Similarity = result.Similarity; diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs index d6161e738..5ab6cfd6d 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Web/DefinitionResponse.cs @@ -17,6 +17,7 @@ namespace Tango.TCC.BL.Web public double SimilarityTolerance { get; set; } public CardDetectionHistogramMethods HistogramMethod { get; set; } public bool EnableDoubleChecking { get; set; } + public bool EnforceBarcodeDetection { get; set; } public DefinitionResponse() { @@ -27,6 +28,7 @@ namespace Tango.TCC.BL.Web SimilarityTolerance = 50; HistogramMethod = CardDetectionHistogramMethods.Chi_Square; EnableDoubleChecking = true; + EnforceBarcodeDetection = true; } } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp index 174d2d549..25b523e58 100644 Binary files a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h index 86d44ca34..90f3239be 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h @@ -17,6 +17,7 @@ namespace Tango property double SimilarityTolerance; property int HistogramMethod; property bool EnableDoubleChecking; + property bool EnforceBarcodeDetection; }; } } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs b/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs index b3a4d4808..0c4111189 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/Controllers/ColorDetectionController.cs @@ -33,6 +33,7 @@ namespace Tango.TCC.Service.Controllers HistogramMethod = TCCServiceConfig.HISTOGRAM_METHOD, SimilarityTolerance = TCCServiceConfig.SIMILARITY_TOLERANCE, EnableDoubleChecking = TCCServiceConfig.ENABLE_DOUBLE_CHECKING, + EnforceBarcodeDetection = TCCServiceConfig.ENFORCE_BARCODE_DETECTION, }; } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs b/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs index 8bedaefe9..14e9ef8cc 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/TCCServiceConfig.cs @@ -67,6 +67,11 @@ namespace Tango.TCC.Service /// public static bool ENABLE_DOUBLE_CHECKING => bool.Parse(ConfigurationManager.AppSettings[nameof(ENABLE_DOUBLE_CHECKING)].ToString()); + /// + /// Gets a value indicating whether the card will be detected only when barcode detection is successful. + /// + public static bool ENFORCE_BARCODE_DETECTION => bool.Parse(ConfigurationManager.AppSettings[nameof(ENFORCE_BARCODE_DETECTION)].ToString()); + /// /// Gets the mobile application ID. /// diff --git a/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config b/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config index d2433dc9b..291a5baec 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config +++ b/Software/Visual_Studio/TCC/Tango.TCC.Service/Web.config @@ -38,6 +38,7 @@ + diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Dialogs/CommonOpenFileDialog.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Dialogs/CommonOpenFileDialog.cs deleted file mode 100644 index 6664e2d9c..000000000 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Dialogs/CommonOpenFileDialog.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Tango.Scripting.IDE.Dialogs -{ - //internal class CommonOpenFileDialog - //{ - // public string InitialDirectory { get; internal set; } - //} -} \ No newline at end of file diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/IDESettings.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/IDESettings.cs new file mode 100644 index 000000000..608ca0bdc --- /dev/null +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/IDESettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.IDE +{ + public class IDESettings + { + } +} diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectType.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectType.cs index 4b3f6085b..86efc4330 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectType.cs +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectType.cs @@ -19,6 +19,7 @@ namespace Tango.Scripting.IDE public abstract string Name { get; } public abstract string Description { get; } + public abstract string Extention { get; } public abstract BitmapSource SmallImage { get; } public abstract BitmapSource LargeImage { get; } diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs index 42e40756e..6fe1316f4 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/StubProjectType.cs @@ -16,7 +16,7 @@ namespace Tango.Scripting.IDE.ProjectTypes { StubProject project = new StubProject(); - project.FilePath = projectPath; + project.FilePath = projectPath + Extention; ; var referenceAssembliesItem = new ReferenceAssembliesItem(); @@ -41,6 +41,7 @@ namespace Tango.Scripting.IDE.ProjectTypes public override string Name => "Stub Project"; public override string Description => "Create a stub project template."; + public override string Extention => ".stub"; public override BitmapSource SmallImage => GetImage("Images/stub_project_32.png"); public override BitmapSource LargeImage => GetImage("Images/stub_project_126.png"); } diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs index c4cdcc6ab..42bab7059 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ProjectTypes/UnitTestProjectType.cs @@ -16,7 +16,7 @@ namespace Tango.Scripting.IDE.ProjectTypes { UnitTestProject project = new UnitTestProject(); - project.FilePath = projectPath; + project.FilePath = projectPath + Extention; ; var referenceAssembliesItem = new ReferenceAssembliesItem(); @@ -38,6 +38,7 @@ namespace Tango.Scripting.IDE.ProjectTypes public override string Name => "Unit Test Project"; public override string Description => "Create a unit test project template."; + public override string Extention => ".unit"; public override BitmapSource SmallImage => GetImage("Images/unitTest.png"); public override BitmapSource LargeImage => GetImage("Images/unitTest_126.png"); diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs index d8a744fea..b156371c5 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/ScriptIDEViewVM.cs @@ -89,6 +89,7 @@ namespace Tango.Scripting.IDE RegisterProjectType(new UnitTestProjectType()); Solution = new Solution(); + Solution.SolutionLocation = @"C:\Test"; Solution.Projects.Add(_projectTypes.First().NewProject("Test Project.stub")); //Init Commands @@ -150,21 +151,29 @@ namespace Tango.Scripting.IDE if (vm.DialogResult) { - + Solution newSolution = new Solution(); + newSolution.Name = vm.SolutionName; + newSolution.SolutionLocation = vm.ProjectLocation; + Solution = newSolution; + StringBuilder builder = new StringBuilder(vm.ProjectLocation); + builder.AppendFormat(@"\{0}", vm.ProjectName); + Solution.Projects.Add(vm.SelectedProjectType.NewProject(builder.ToString())); } } private async void AddProject() { var vm = await NotificationManager.ShowDialog(new AddProjectViewVM() { - //ProjectLocation = Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.FullName + ProjectLocation = Solution.SolutionLocation //ProjectLocation = Path.GetDirectoryName(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase)) //ProjectLocation = "Current Solution folder..." }); if (vm.DialogResult) { - + StringBuilder builder = new StringBuilder(vm.ProjectLocation); + builder.AppendFormat(@"\{0}", vm.ProjectName); + Solution.Projects.Add(vm.SelectedProjectType.NewProject(builder.ToString())); } } public bool IsSolutionProject(IProject SelectedItem) diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Solution.cs b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Solution.cs index eded27413..cd7806698 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Solution.cs +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Solution.cs @@ -10,6 +10,8 @@ namespace Tango.Scripting.IDE public class Solution { public ObservableCollection Projects { get; set; } + public string Name{get; set;} + public string SolutionLocation { get; set; } public Solution() { diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj index ea47eb8f7..803815df4 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Tango.Scripting.IDE.csproj @@ -77,7 +77,6 @@ - NewProjectView.xaml @@ -88,6 +87,7 @@ + diff --git a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml index 218a92302..683391afd 100644 --- a/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml +++ b/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.IDE/Windows/DialogWindow.xaml @@ -6,13 +6,13 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Tango.Scripting.IDE.Windows" mc:Ignorable="d" - Title="Some Title" Height="800" Width="800" + Title="Some Title" Height="720" Width="1280" SizeToContent="WidthAndHeight" ResizeMode="NoResize" ShowMaxRestoreButton="False" ShowMinButton="False" ShowCloseButton="False" - BorderThickness="1" BorderBrush="Gray"> + BorderThickness="1" BorderBrush="Gray" TitleCaps="False"> diff --git a/Software/Visual_Studio/Tango.EmbroideryUI/app.config b/Software/Visual_Studio/Tango.EmbroideryUI/app.config index 72700d700..029361710 100644 --- a/Software/Visual_Studio/Tango.EmbroideryUI/app.config +++ b/Software/Visual_Studio/Tango.EmbroideryUI/app.config @@ -12,7 +12,7 @@ - + @@ -20,36 +20,44 @@ - + - + - + - + - + - + - + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Explorer/app.config b/Software/Visual_Studio/Tango.Explorer/app.config index 462d17b27..b5963077e 100644 --- a/Software/Visual_Studio/Tango.Explorer/app.config +++ b/Software/Visual_Studio/Tango.Explorer/app.config @@ -12,7 +12,7 @@ - + @@ -20,27 +20,39 @@ - + - + - + - + - + - + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Tango.SharedUI/DialogViewVM.cs b/Software/Visual_Studio/Tango.SharedUI/DialogViewVM.cs index 1854b410a..3154be545 100644 --- a/Software/Visual_Studio/Tango.SharedUI/DialogViewVM.cs +++ b/Software/Visual_Studio/Tango.SharedUI/DialogViewVM.cs @@ -24,7 +24,7 @@ namespace Tango.SharedUI { CanClose = true; CloseCommand = new RelayCommand(Cancel, (x) => CanClose); - OKCommand = new RelayCommand(Accept, (x) => CanClose); + OKCommand = new RelayCommand(Accept, (x) => CanClose && CanOK()); } private bool _canClose; @@ -37,6 +37,14 @@ namespace Tango.SharedUI set { _canClose = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } + /// + /// Determines whether this instance can invoke the OK command. + /// + protected virtual bool CanOK() + { + return true; + } + /// /// Gets a value indicating whether the dialog has been confirmed. /// diff --git a/Software/Visual_Studio/Tango.SharedUI/app.config b/Software/Visual_Studio/Tango.SharedUI/app.config index c5423fe28..1daa92bd2 100644 --- a/Software/Visual_Studio/Tango.SharedUI/app.config +++ b/Software/Visual_Studio/Tango.SharedUI/app.config @@ -16,7 +16,7 @@ - + @@ -24,30 +24,38 @@ - + - + - + - + - + - + + + + + + + + + diff --git a/Software/Visual_Studio/Tango.Stubs/app.config b/Software/Visual_Studio/Tango.Stubs/app.config index 203a0c7bc..6232aae7c 100644 --- a/Software/Visual_Studio/Tango.Stubs/app.config +++ b/Software/Visual_Studio/Tango.Stubs/app.config @@ -20,7 +20,7 @@ - + @@ -28,36 +28,44 @@ - + - + - + - + - + - + - + + + + + + + + + diff --git a/Software/Visual_Studio/Tango.Touch/app.config b/Software/Visual_Studio/Tango.Touch/app.config index 72700d700..029361710 100644 --- a/Software/Visual_Studio/Tango.Touch/app.config +++ b/Software/Visual_Studio/Tango.Touch/app.config @@ -12,7 +12,7 @@ - + @@ -20,36 +20,44 @@ - + - + - + - + - + - + - + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.UnitTesting/App.config b/Software/Visual_Studio/Tango.UnitTesting/App.config index 074da9ac0..1261bcbe9 100644 --- a/Software/Visual_Studio/Tango.UnitTesting/App.config +++ b/Software/Visual_Studio/Tango.UnitTesting/App.config @@ -63,7 +63,7 @@ - + @@ -71,27 +71,27 @@ - + - + - + - + - + - + @@ -105,6 +105,18 @@ + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln index ef5d79e33..399ca9aa7 100644 --- a/Software/Visual_Studio/Tango.sln +++ b/Software/Visual_Studio/Tango.sln @@ -287,6 +287,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.MachineStudio.ColorCa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tango.TCC.CardDetector", "TCC\Tango.TCC.CardDetector\Tango.TCC.CardDetector.vcxproj", "{BB268536-9E03-46A4-9B11-6025211D87F0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripting", "Scripting", "{3D750293-C243-48F6-9112-A6B3FF650C0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Scripting", "Scripting\Tango.Scripting\Tango.Scripting.csproj", "{1E938FD2-C669-4738-98C9-77F96CE4D451}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Scripting.Editors", "Scripting\Tango.Scripting.Editors\Tango.Scripting.Editors.csproj", "{DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Scripting.IDE", "Scripting\Tango.Scripting.IDE\Tango.Scripting.IDE.csproj", "{C9F60285-91FB-4293-BCF5-164D76755CDD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Scripting.IDE.UI", "Scripting\Tango.Scripting.IDE.UI\Tango.Scripting.IDE.UI.csproj", "{B0EFE7A0-7039-4DC4-8B39-465E521299F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AppVeyor|Any CPU = AppVeyor|Any CPU @@ -5099,6 +5109,166 @@ Global {BB268536-9E03-46A4-9B11-6025211D87F0}.Release|x64.Build.0 = Release|x64 {BB268536-9E03-46A4-9B11-6025211D87F0}.Release|x86.ActiveCfg = Release|Win32 {BB268536-9E03-46A4-9B11-6025211D87F0}.Release|x86.Build.0 = Release|Win32 + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|ARM.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|x64.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.AppVeyor|x86.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|ARM.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|ARM.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|ARM64.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|x64.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Debug|x86.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|Any CPU.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|ARM.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|ARM.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|ARM64.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|ARM64.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|x64.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|x64.Build.0 = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|x86.ActiveCfg = Release|Any CPU + {1E938FD2-C669-4738-98C9-77F96CE4D451}.Release|x86.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|ARM.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|x64.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.AppVeyor|x86.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|ARM.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|ARM.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|ARM64.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|x64.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Debug|x86.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|Any CPU.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|ARM.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|ARM.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|ARM64.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|ARM64.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|x64.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|x64.Build.0 = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|x86.ActiveCfg = Release|Any CPU + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0}.Release|x86.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|ARM.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|x64.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.AppVeyor|x86.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|ARM.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|ARM64.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|x64.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|x64.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|x86.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Debug|x86.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|Any CPU.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|ARM.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|ARM.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|ARM64.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|ARM64.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|x64.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|x64.Build.0 = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|x86.ActiveCfg = Release|Any CPU + {C9F60285-91FB-4293-BCF5-164D76755CDD}.Release|x86.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|ARM.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|x64.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.AppVeyor|x86.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|ARM.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|ARM64.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|x64.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Debug|x86.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|Any CPU.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|ARM.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|ARM.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|ARM64.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|ARM64.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|x64.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|x64.Build.0 = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|x86.ActiveCfg = Release|Any CPU + {B0EFE7A0-7039-4DC4-8B39-465E521299F6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5190,14 +5360,18 @@ Global {DD19A7B3-E4B0-444E-98D4-D1C346442E63} = {DA2B8F5D-AE3C-4E78-AEEB-FC17D0582690} {1B87CA53-50BD-4C48-A8C7-FBB9F1419AFF} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8} {BB268536-9E03-46A4-9B11-6025211D87F0} = {DA2B8F5D-AE3C-4E78-AEEB-FC17D0582690} + {1E938FD2-C669-4738-98C9-77F96CE4D451} = {3D750293-C243-48F6-9112-A6B3FF650C0D} + {DA62FA39-668B-47A6-B0F2-D2C1DAF777B0} = {3D750293-C243-48F6-9112-A6B3FF650C0D} + {C9F60285-91FB-4293-BCF5-164D76755CDD} = {3D750293-C243-48F6-9112-A6B3FF650C0D} + {B0EFE7A0-7039-4DC4-8B39-465E521299F6} = {3D750293-C243-48F6-9112-A6B3FF650C0D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6} - BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear - BuildVersion_UpdateAssemblyVersion = True - BuildVersion_UpdateFileVersion = False - BuildVersion_StartDate = 2000/1/1 - BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs BuildVersion_UseGlobalSettings = False + BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs + BuildVersion_StartDate = 2000/1/1 + BuildVersion_UpdateFileVersion = False + BuildVersion_UpdateAssemblyVersion = True + BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear + SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6} EndGlobalSection EndGlobal diff --git a/Software/Visual_Studio/Utilities/Tango.EmbroideryViewer/App.config b/Software/Visual_Studio/Utilities/Tango.EmbroideryViewer/App.config index cfaef6c7d..04864d66e 100644 --- a/Software/Visual_Studio/Utilities/Tango.EmbroideryViewer/App.config +++ b/Software/Visual_Studio/Utilities/Tango.EmbroideryViewer/App.config @@ -15,7 +15,7 @@ - + @@ -23,36 +23,44 @@ - + - + - + - + - + - + - + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Utilities/Tango.FirmwarePackageGenerator/App.config b/Software/Visual_Studio/Utilities/Tango.FirmwarePackageGenerator/App.config index be3d2b956..c924d45e9 100644 --- a/Software/Visual_Studio/Utilities/Tango.FirmwarePackageGenerator/App.config +++ b/Software/Visual_Studio/Utilities/Tango.FirmwarePackageGenerator/App.config @@ -15,7 +15,7 @@ - + @@ -23,27 +23,39 @@ - + - + - + - + - + - + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Utilities/Tango.ILMerge.UI/App.config b/Software/Visual_Studio/Utilities/Tango.ILMerge.UI/App.config index cfaef6c7d..04864d66e 100644 --- a/Software/Visual_Studio/Utilities/Tango.ILMerge.UI/App.config +++ b/Software/Visual_Studio/Utilities/Tango.ILMerge.UI/App.config @@ -15,7 +15,7 @@ - + @@ -23,36 +23,44 @@ - + - + - + - + - + - + - + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Utilities/Tango.MachineEM.UI/App.config b/Software/Visual_Studio/Utilities/Tango.MachineEM.UI/App.config index ef214165a..bf8f9dfef 100644 --- a/Software/Visual_Studio/Utilities/Tango.MachineEM.UI/App.config +++ b/Software/Visual_Studio/Utilities/Tango.MachineEM.UI/App.config @@ -23,7 +23,7 @@ - + @@ -31,31 +31,31 @@ - + - + - + - + - + - + - + @@ -65,6 +65,14 @@ + + + + + + + + diff --git a/Software/Visual_Studio/Utilities/Tango.Protobuf.UI/App.config b/Software/Visual_Studio/Utilities/Tango.Protobuf.UI/App.config index b53d37a13..b51712fe8 100644 --- a/Software/Visual_Studio/Utilities/Tango.Protobuf.UI/App.config +++ b/Software/Visual_Studio/Utilities/Tango.Protobuf.UI/App.config @@ -15,7 +15,7 @@ - + @@ -23,36 +23,44 @@ - + - + - + - + - + - + - + + + + + + + + + diff --git a/Software/Visual_Studio/Utilities/Tango.RemoteRunner.UI/App.config b/Software/Visual_Studio/Utilities/Tango.RemoteRunner.UI/App.config index 862732d18..eb2a9c0ce 100644 --- a/Software/Visual_Studio/Utilities/Tango.RemoteRunner.UI/App.config +++ b/Software/Visual_Studio/Utilities/Tango.RemoteRunner.UI/App.config @@ -15,7 +15,7 @@ - + @@ -23,27 +23,27 @@ - + - + - + - + - + - + @@ -51,7 +51,7 @@ - + @@ -61,6 +61,14 @@ + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Utilities/Tango.Stubs.UI/App.config b/Software/Visual_Studio/Utilities/Tango.Stubs.UI/App.config index e060dcc60..55258e455 100644 --- a/Software/Visual_Studio/Utilities/Tango.Stubs.UI/App.config +++ b/Software/Visual_Studio/Utilities/Tango.Stubs.UI/App.config @@ -155,7 +155,7 @@ - + @@ -163,27 +163,27 @@ - + - + - + - + - + - + @@ -193,6 +193,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Utilities/Tango.TransportRouter.UI/App.config b/Software/Visual_Studio/Utilities/Tango.TransportRouter.UI/App.config index 45b81480f..b10bfd6db 100644 --- a/Software/Visual_Studio/Utilities/Tango.TransportRouter.UI/App.config +++ b/Software/Visual_Studio/Utilities/Tango.TransportRouter.UI/App.config @@ -19,7 +19,7 @@ - + @@ -27,31 +27,31 @@ - + - + - + - + - + - + - + @@ -61,6 +61,14 @@ + + + + + + + + diff --git a/Software/Visual_Studio/Utilities/Tango.UITests/App.config b/Software/Visual_Studio/Utilities/Tango.UITests/App.config index f2a26c582..324ce7b21 100644 --- a/Software/Visual_Studio/Utilities/Tango.UITests/App.config +++ b/Software/Visual_Studio/Utilities/Tango.UITests/App.config @@ -26,7 +26,7 @@ - + @@ -34,31 +34,31 @@ - + - + - + - + - + - + - + @@ -84,6 +84,14 @@ + + + + + + + + diff --git a/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/App.config b/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/App.config index f74b85d15..44b564d99 100644 --- a/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/App.config +++ b/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/App.config @@ -43,7 +43,7 @@ - + @@ -51,27 +51,39 @@ - + - + - + - + - + - + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Web.config b/Software/Visual_Studio/Web/Tango.MachineService/Web.config index 58a8c48af..c8d85ea20 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Web.config +++ b/Software/Visual_Studio/Web/Tango.MachineService/Web.config @@ -49,7 +49,7 @@ - + @@ -116,7 +116,7 @@ - + @@ -124,27 +124,27 @@ - + - + - + - + - + - + @@ -162,6 +162,18 @@ + + + + + + + + + + + + -- cgit v1.3.1 From 3c5f32456c72b26497c05bb35fd64e3c77c6b0f5 Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Mon, 15 Apr 2019 12:06:30 +0300 Subject: Now using the latest RealTimeGraphX library! + infinity check! --- .../Graph/WpfGraphController.cs | 36 -- .../Tango.MachineStudio.ColorCapture.csproj | 9 +- .../ViewModels/MainViewVM.cs | 16 +- .../Views/MainView.xaml | 11 +- .../Views/MainView.xaml.cs | 1 - .../Tango.MachineStudio.Developer.csproj | 6 +- .../Editors/MultiGraphElementEditor.xaml | 2 +- .../Editors/SingleGraphElementEditor.xaml | 4 +- .../Tango.MachineStudio.Technician.csproj | 14 +- .../TechGraphController.cs | 15 +- .../TechItems/MultiGraphItem.cs | 3 +- .../TechItems/SingleGraphItem.cs | 3 +- .../ViewModels/MachineTechViewVM.cs | 11 +- .../Controls/IRealTimeGraph.cs | 35 -- .../Controls/RealTimeGraphControl.xaml | 65 --- .../Controls/RealTimeGraphControl.xaml.cs | 77 --- .../Controls/WpfGraphControl.cs | 88 ++++ .../Resources/MaterialDesign.xaml | 29 +- .../Tango.MachineStudio.Common.csproj | 24 +- .../Tango.MachineStudio.Common/Themes/Generic.xaml | 87 ++++ .../Tango.MachineStudio.UI.csproj | 6 +- .../RealTimeGraphX.WPF.Demo/App.config | 6 + .../RealTimeGraphX.WPF.Demo/App.xaml | 9 + .../RealTimeGraphX.WPF.Demo/App.xaml.cs | 17 + .../RealTimeGraphX.WPF.Demo/MainWindow.xaml | 112 +++++ .../RealTimeGraphX.WPF.Demo/MainWindow.xaml.cs | 29 ++ .../RealTimeGraphX.WPF.Demo/MainWindowVM.cs | 107 ++++ .../Properties/AssemblyInfo.cs | 55 +++ .../Properties/Resources.Designer.cs | 71 +++ .../Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../RealTimeGraphX.WPF.Demo.csproj | 116 +++++ .../RealTimeGraphX.WPF.Demo/Themes/Generic.xaml | 53 ++ .../RealTimeGraphX.WPF.Demo/WpfGraphControl.cs | 39 ++ .../RealTimeGraphX.WPF/ExtensionMethods.cs | 79 +++ .../RealTimeGraphX.WPF/Properties/AssemblyInfo.cs | 55 +++ .../Properties/Resources.Designer.cs | 62 +++ .../RealTimeGraphX.WPF/Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj | 97 ++++ .../RealTimeGraphX.WPF/Themes/Generic.xaml | 143 ++++++ .../RealTimeGraphX.WPF/WpfGraphAxisControl.cs | 172 +++++++ .../RealTimeGraphX.WPF/WpfGraphAxisPanel.cs | 115 +++++ .../RealTimeGraphX.WPF/WpfGraphAxisTickData.cs | 98 ++++ .../RealTimeGraphX.WPF/WpfGraphComponentBase.cs | 66 +++ .../RealTimeGraphX.WPF/WpfGraphController.cs | 22 + .../RealTimeGraphX.WPF/WpfGraphDataSeries.cs | 139 ++++++ .../RealTimeGraphX.WPF/WpfGraphGridLines.cs | 92 ++++ .../RealTimeGraphX.WPF/WpfGraphSurface.cs | 380 ++++++++++++++ .../RealTimeGraphX/DataPoints/DoubleDataPoint.cs | 178 +++++++ .../RealTimeGraphX/DataPoints/FloatDataPoint.cs | 178 +++++++ .../RealTimeGraphX/DataPoints/Int32DataPoint.cs | 178 +++++++ .../RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs | 180 +++++++ .../EventArguments/RangeChangedEventArgs.cs | 58 +++ .../RealTimeGraphX/GraphCommand.cs | 77 +++ .../RealTimeGraphX/GraphController.cs | 543 +++++++++++++++++++++ .../RealTimeGraphX/GraphDataPoint.cs | 412 ++++++++++++++++ .../RealTimeGraphX/GraphDataPointHelper.cs | 48 ++ .../RealTimeGraphX/GraphDataPointTypeConverter.cs | 82 ++++ .../RealTimeGraphX/GraphDataQueue.cs | 63 +++ .../RealTimeGraphX/GraphObject.cs | 42 ++ .../RealTimeGraphX/GraphRange.cs | 138 ++++++ .../RealTimeGraphX/GraphRenderer.cs | 76 +++ .../RealTimeGraphX/GraphTransform.cs | 39 ++ .../RealTimeGraphX/IGraphComponent.cs | 10 + .../RealTimeGraphX/IGraphController.cs | 185 +++++++ .../RealTimeGraphX/IGraphDataPoint.cs | 94 ++++ .../RealTimeGraphX/IGraphDataSeries.cs | 31 ++ .../RealTimeGraphX/IGraphRenderer.cs | 41 ++ .../RealTimeGraphX/IGraphSurface.cs | 64 +++ .../RealTimeGraphX/RealTimeGraphX.csproj | 7 + .../Renderers/ScrollingLineRenderer.cs | 84 ++++ .../RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj | 6 - Software/Visual_Studio/Tango.sln | 269 +++++----- 76 files changed, 5612 insertions(+), 455 deletions(-) delete mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs delete mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/IRealTimeGraph.cs delete mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml delete mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml.cs create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/WpfGraphControl.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.config create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindowVM.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.Designer.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.resx create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.Designer.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.settings create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/ExtensionMethods.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.Designer.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.resx create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.Designer.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.settings create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Themes/Generic.xaml create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisControl.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisPanel.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisTickData.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphComponentBase.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphController.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphDataSeries.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphGridLines.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphSurface.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/DoubleDataPoint.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/FloatDataPoint.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/Int32DataPoint.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/EventArguments/RangeChangedEventArgs.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphCommand.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphController.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPoint.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointHelper.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointTypeConverter.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataQueue.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphObject.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRange.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRenderer.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphTransform.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphComponent.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphController.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataPoint.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataSeries.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphRenderer.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphSurface.cs create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/RealTimeGraphX.csproj create mode 100644 Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/Renderers/ScrollingLineRenderer.cs (limited to 'Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common') diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs deleted file mode 100644 index 63ce7035e..000000000 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using RealTimeGraphX; -using RealTimeGraphX.DataPoints; -using RealTimeGraphX.Renderers; -using RealTimeGraphX.WPF.DataSeries; -using RealTimeGraphX.WPF.Painters; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Media; - -namespace Tango.MachineStudio.ColorCapture.Graph -{ - public class WpfGraphController : GraphControllerBase - { - public WpfGraphController(int refreshRate = 50) - { - AddDataSeries(new WpfDataSeries() - { - StrokeThickness = 1, - Stroke = Colors.DodgerBlue, - }); - - var renderer = new GraphScrollingRenderer() - { - RefreshRate = TimeSpan.FromMilliseconds(refreshRate) - }; - - var painter = new WpfScrollingGraphPainter(); - - ConnectOutput(renderer); - renderer.ConnectOutput(painter); - } - } -} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj index c30520f13..b399d9971 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj @@ -100,7 +100,6 @@ - @@ -139,12 +138,12 @@ {37e4ceab-b54b-451f-b535-04cf7da9c459} ColorMine - - {99d233c5-fee7-418e-9c25-d4584cb52e28} + + {6b9774f7-960d-438e-ad81-c6b9be328d50} RealTimeGraphX.WPF - - {6d55a3b8-46d3-493a-a143-aebd2b98d683} + + {f13a489c-80ee-4cd0-bdd4-92d959215646} RealTimeGraphX diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs index 5ea5747d5..31eec0871 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs @@ -2,6 +2,8 @@ using ColorMine.ColorSpaces.Comparisons; using Microsoft.Win32; using Microsoft.WindowsAPICodePack.Dialogs; +using RealTimeGraphX.DataPoints; +using RealTimeGraphX.WPF; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,7 +18,6 @@ using Tango.Core; using Tango.Core.Commands; using Tango.CSV; using Tango.Logging; -using Tango.MachineStudio.ColorCapture.Graph; using Tango.MachineStudio.ColorCapture.Models; using Tango.MachineStudio.Common; using Tango.MachineStudio.Common.Notifications; @@ -106,7 +107,7 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels public double DeltaE { get; set; } - public WpfGraphController CaptureDeltaEController { get; set; } + public WpfGraphController CaptureDeltaEController { get; set; } public RelayCommand ToggleCameraCommand { get; set; } @@ -169,7 +170,11 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels Benchmarks = new ObservableCollection(); _cardDetector = new CardDetector(); ToggleCameraCommand = new RelayCommand(ToggleCamera); - CaptureDeltaEController = new WpfGraphController(); + CaptureDeltaEController = new WpfGraphController(); + CaptureDeltaEController.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Stroke = System.Windows.Media.Colors.DodgerBlue, + }); CaptureDeltaEController.Range.AutoY = true; CaptureDeltaEController.Range.MaximumX = 1000; @@ -461,6 +466,11 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels } } + if (double.IsInfinity(DeltaE)) + { + DeltaE = 0; + } + CaptureDeltaEController.PushData(_sampleCounter++, DeltaE); } diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml index ba8e3f281..afdac7e18 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml @@ -9,12 +9,11 @@ xmlns:converters="clr-namespace:Tango.SharedUI.Converters;assembly=Tango.SharedUI" xmlns:vm="clr-namespace:Tango.MachineStudio.ColorCapture.ViewModels" xmlns:mahapps="http://metro.mahapps.com/winfx/xaml/controls" - xmlns:graphX="clr-namespace:RealTimeGraphX.WPF.Surfaces;assembly=RealTimeGraphX.WPF" - xmlns:componentsX="clr-namespace:RealTimeGraphX.WPF.Components;assembly=RealTimeGraphX.WPF" + xmlns:commonControls="clr-namespace:Tango.MachineStudio.Common.Controls;assembly=Tango.MachineStudio.Common" xmlns:controls="clr-namespace:Tango.MachineStudio.ColorCapture.Controls" xmlns:sharedControls="clr-namespace:Tango.SharedUI.Controls;assembly=Tango.SharedUI" xmlns:tcc="clr-namespace:Tango.TCC.BL;assembly=Tango.TCC.BL" - xmlns:realtimeGraphX="clr-namespace:RealTimeGraphX.WPF.Surfaces;assembly=RealTimeGraphX.WPF" + xmlns:realtimeGraphX="clr-namespace:RealTimeGraphX.WPF;assembly=RealTimeGraphX.WPF" xmlns:global="clr-namespace:Tango.MachineStudio.ColorCapture" xmlns:local="clr-namespace:Tango.MachineStudio.ColorCapture.Views" mc:Ignorable="d" @@ -435,14 +434,14 @@ - + - + - + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs index 05b5daf7e..aa7ff5d49 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs @@ -29,7 +29,6 @@ namespace Tango.MachineStudio.ColorCapture.Views Loaded += (_, __) => { _vm = DataContext as MainViewVM; - _vm.CaptureDeltaEController.Output.Output.ConnectOutput(Graph); }; } } diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Developer/Tango.MachineStudio.Developer.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Developer/Tango.MachineStudio.Developer.csproj index 190e8d0fe..cd00557b1 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Developer/Tango.MachineStudio.Developer.csproj +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Developer/Tango.MachineStudio.Developer.csproj @@ -196,10 +196,6 @@ {37e4ceab-b54b-451f-b535-04cf7da9c459} ColorMine - - {b9ae25d6-be35-492f-9079-21a7f3e6f7cc} - RealTimeGraphEx - {BB2ABB74-BA58-4812-83AA-EC8171F42DF4} Tango.AutoComplete @@ -361,7 +357,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/MultiGraphElementEditor.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/MultiGraphElementEditor.xaml index 22fd8eaa8..9b9334355 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/MultiGraphElementEditor.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/MultiGraphElementEditor.xaml @@ -32,7 +32,7 @@ - + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/SingleGraphElementEditor.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/SingleGraphElementEditor.xaml index ca2481de6..622ddfa8b 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/SingleGraphElementEditor.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Editors/SingleGraphElementEditor.xaml @@ -32,9 +32,9 @@ - + - + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Tango.MachineStudio.Technician.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Tango.MachineStudio.Technician.csproj index 7e6f4e111..ff45bcbf9 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Tango.MachineStudio.Technician.csproj +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/Tango.MachineStudio.Technician.csproj @@ -522,16 +522,12 @@ - - {b9ae25d6-be35-492f-9079-21a7f3e6f7cc} - RealTimeGraphEx - - - {99d233c5-fee7-418e-9c25-d4584cb52e28} + + {6b9774f7-960d-438e-ad81-c6b9be328d50} RealTimeGraphX.WPF - - {6d55a3b8-46d3-493a-a143-aebd2b98d683} + + {f13a489c-80ee-4cd0-bdd4-92d959215646} RealTimeGraphX @@ -728,7 +724,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechGraphController.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechGraphController.cs index 28c75f79f..ca132095c 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechGraphController.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechGraphController.cs @@ -1,8 +1,7 @@ using RealTimeGraphX; using RealTimeGraphX.DataPoints; using RealTimeGraphX.Renderers; -using RealTimeGraphX.WPF.DataSeries; -using RealTimeGraphX.WPF.Painters; +using RealTimeGraphX.WPF; using System; using System.Collections.Generic; using System.Linq; @@ -11,19 +10,11 @@ using System.Threading.Tasks; namespace Tango.MachineStudio.Technician { - public class TechGraphController : GraphControllerBase + public class TechGraphController : WpfGraphController { public TechGraphController(int refreshRate = 50) { - var renderer = new GraphScrollingRenderer() - { - RefreshRate = TimeSpan.FromMilliseconds(refreshRate) - }; - - var painter = new WpfScrollingGraphPainter(); - - ConnectOutput(renderer); - renderer.ConnectOutput(painter); + RefreshRate = TimeSpan.FromMilliseconds(50); } } } diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/MultiGraphItem.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/MultiGraphItem.cs index 377738d09..1412175a0 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/MultiGraphItem.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/MultiGraphItem.cs @@ -1,4 +1,5 @@ using RealTimeGraphX.DataPoints; +using RealTimeGraphX.WPF; using System; using System.Collections.Generic; using System.Linq; @@ -213,7 +214,7 @@ namespace Tango.MachineStudio.Technician.TechItems { if (Editor != null) { - var controller = Editor.InnerGraph.Controller; + var controller = Editor.InnerGraph.Controller as WpfGraphController; controller.Range.MaximumX = new TimeSpanDataPoint(TimeSpan.FromSeconds(_duration)); controller.Range.MinimumY = new DoubleDataPoint(_min); diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/SingleGraphItem.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/SingleGraphItem.cs index 808dadb68..8ccd5e718 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/SingleGraphItem.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/TechItems/SingleGraphItem.cs @@ -1,4 +1,5 @@ using RealTimeGraphX.DataPoints; +using RealTimeGraphX.WPF; using System; using System.Collections.Generic; using System.Linq; @@ -217,7 +218,7 @@ namespace Tango.MachineStudio.Technician.TechItems { if (Editor != null) { - var controller = Editor.InnerGraph.Controller; + var controller = Editor.InnerGraph.Controller as WpfGraphController; ; controller.Range.MaximumX = new TimeSpanDataPoint(TimeSpan.FromSeconds(_duration)); controller.Range.MinimumY = new DoubleDataPoint(_min); diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/ViewModels/MachineTechViewVM.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/ViewModels/MachineTechViewVM.cs index e78047563..72e43d7b0 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/ViewModels/MachineTechViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/ViewModels/MachineTechViewVM.cs @@ -1,6 +1,5 @@ using Google.Protobuf.Collections; using Microsoft.Win32; -using RealTimeGraphEx.Controllers; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -36,9 +35,9 @@ using Tango.MachineStudio.Technician.Models; using Tango.Logging; using Microsoft.WindowsAPICodePack.Dialogs; using RealTimeGraphX; -using RealTimeGraphX.WPF.DataSeries; using RealTimeGraphX.DataPoints; using Tango.MachineStudio.Technician.Views; +using RealTimeGraphX.WPF; namespace Tango.MachineStudio.Technician.ViewModels { @@ -1024,7 +1023,7 @@ namespace Tango.MachineStudio.Technician.ViewModels var editor = element as SingleGraphElementEditor; TechGraphController controller = new TechGraphController(); - controller.AddDataSeries(new WpfDataSeries() + controller.DataSeriesCollection.Add(new WpfGraphDataSeries() { Stroke = Colors.DodgerBlue, }); @@ -1043,7 +1042,7 @@ namespace Tango.MachineStudio.Technician.ViewModels for (int i = 0; i < graphItem.TechMonitor.ChannelCount; i++) { - controller.AddDataSeries(new WpfDataSeries() + controller.DataSeriesCollection.Add(new WpfGraphDataSeries() { Stroke = ColorHelper.GetRandomColor(), Name = graphItem.TechMonitor.Name.First() + (i + 1).ToString(), @@ -1437,7 +1436,7 @@ namespace Tango.MachineStudio.Technician.ViewModels controller.Range.MaximumY = item.TechMonitor.Max; controller.Range.MaximumX = TimeSpan.FromSeconds(10); - controller.AddDataSeries(new WpfDataSeries() + controller.DataSeriesCollection.Add(new WpfGraphDataSeries() { Stroke = Colors.DodgerBlue, }); @@ -1498,7 +1497,7 @@ namespace Tango.MachineStudio.Technician.ViewModels for (int i = 0; i < item.TechMonitor.ChannelCount; i++) { - controller.AddDataSeries(new WpfDataSeries() + controller.DataSeriesCollection.Add(new WpfGraphDataSeries() { Stroke = ColorHelper.GetRandomColor(), Name = item.TechMonitor.Name.First() + (i + 1).ToString(), diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/IRealTimeGraph.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/IRealTimeGraph.cs deleted file mode 100644 index dcf2cdf81..000000000 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/IRealTimeGraph.cs +++ /dev/null @@ -1,35 +0,0 @@ -using RealTimeGraphEx; -using RealTimeGraphEx.Controllers; -using RealTimeGraphX; -using RealTimeGraphX.WPF.DataSeries; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.MachineStudio.Common.Controls -{ - public interface IRealTimeGraph - { - /// - /// Gets or sets the name of the sensor. - /// - String DisplayName { get; set; } - - /// - /// Gets or sets the tag. - /// - Object Tag { get; set; } - - /// - /// Gets or sets the sensor units. - /// - String DisplayUnits { get; set; } - - /// - /// Gets or sets the inner graph controller. - /// - IGraphController Controller { get; set; } - } -} diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml deleted file mode 100644 index b5e809a3e..000000000 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml.cs deleted file mode 100644 index f82196fed..000000000 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/RealTimeGraphControl.xaml.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -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.Animation; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using RealTimeGraphEx; -using RealTimeGraphEx.Controllers; -using RealTimeGraphX; -using RealTimeGraphX.WPF.DataSeries; - -namespace Tango.MachineStudio.Common.Controls -{ - /// - /// Interaction logic for RealTimeGraphControl.xaml - /// - public partial class RealTimeGraphControl : UserControl, IRealTimeGraph - { - #region Properties - - public String DisplayName - { - get { return (String)GetValue(DisplayNameProperty); } - set { SetValue(DisplayNameProperty, value); } - } - public static readonly DependencyProperty DisplayNameProperty = - DependencyProperty.Register("DisplayName", typeof(String), typeof(RealTimeGraphControl), new PropertyMetadata(null)); - - public String DisplayUnits - { - get { return (String)GetValue(DisplayUnitsProperty); } - set { SetValue(DisplayUnitsProperty, value); } - } - public static readonly DependencyProperty DisplayUnitsProperty = - DependencyProperty.Register("DisplayUnits", typeof(String), typeof(RealTimeGraphControl), new PropertyMetadata(null)); - - public String StringFormat - { - get { return (String)GetValue(StringFormatProperty); } - set { SetValue(StringFormatProperty, value); } - } - public static readonly DependencyProperty StringFormatProperty = - DependencyProperty.Register("StringFormat", typeof(String), typeof(RealTimeGraphControl), new PropertyMetadata("0.0")); - - - - public IGraphController Controller - { - get { return (IGraphController)GetValue(ControllerProperty); } - set { SetValue(ControllerProperty, value); } - } - public static readonly DependencyProperty ControllerProperty = - DependencyProperty.Register("Controller", typeof(IGraphController), typeof(RealTimeGraphControl), new PropertyMetadata(null,(d,e) => (d as RealTimeGraphControl).OnControllerChanged())); - - private void OnControllerChanged() - { - Controller.Output.Output.ConnectOutput(Graph); - } - - #endregion - - public RealTimeGraphControl() - { - InitializeComponent(); - } - } -} diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/WpfGraphControl.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/WpfGraphControl.cs new file mode 100644 index 000000000..68db76cd5 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Controls/WpfGraphControl.cs @@ -0,0 +1,88 @@ +using RealTimeGraphX; +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.Navigation; +using System.Windows.Shapes; + +namespace Tango.MachineStudio.Common.Controls +{ + public class WpfGraphControl : Control + { + /// + /// Gets or sets the graph controller. + /// + public IGraphController Controller + { + get { return (IGraphController)GetValue(ControllerProperty); } + set { SetValue(ControllerProperty, value); } + } + public static readonly DependencyProperty ControllerProperty = + DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphControl), new PropertyMetadata(null)); + + + /// + /// Gets or sets the string format of the y-axis. + /// + public String StringFormat + { + get { return (String)GetValue(StringFormatProperty); } + set { SetValue(StringFormatProperty, value); } + } + public static readonly DependencyProperty StringFormatProperty = + DependencyProperty.Register("StringFormat", typeof(String), typeof(WpfGraphControl), new PropertyMetadata("0.0")); + + + /// + /// Gets or sets the display name. + /// + public String DisplayName + { + get { return (String)GetValue(DisplayNameProperty); } + set { SetValue(DisplayNameProperty, value); } + } + public static readonly DependencyProperty DisplayNameProperty = + DependencyProperty.Register("DisplayName", typeof(String), typeof(WpfGraphControl), new PropertyMetadata(null)); + + + /// + /// Gets or sets the display units. + /// + public String DisplayUnits + { + get { return (String)GetValue(DisplayUnitsProperty); } + set { SetValue(DisplayUnitsProperty, value); } + } + public static readonly DependencyProperty DisplayUnitsProperty = + DependencyProperty.Register("DisplayUnits", typeof(String), typeof(WpfGraphControl), new PropertyMetadata(null)); + + /// + /// Gets or sets the graph label visibility. + /// + public Visibility GraphLabelVisibility + { + get { return (Visibility)GetValue(GraphLabelVisibilityProperty); } + set { SetValue(GraphLabelVisibilityProperty, value); } + } + public static readonly DependencyProperty GraphLabelVisibilityProperty = + DependencyProperty.Register("GraphLabelVisibility", typeof(Visibility), typeof(WpfGraphControl), new PropertyMetadata(Visibility.Visible)); + + + /// + /// Initializes the class. + /// + static WpfGraphControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphControl), new FrameworkPropertyMetadata(typeof(WpfGraphControl))); + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Resources/MaterialDesign.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Resources/MaterialDesign.xaml index 2f65fe817..d90d03545 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Resources/MaterialDesign.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Resources/MaterialDesign.xaml @@ -235,39 +235,12 @@ - - - - - - - - - - - + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj index 4cf1855ca..e2bf7792d 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj @@ -60,6 +60,7 @@ + @@ -87,6 +88,7 @@ MachineView.xaml + @@ -100,11 +102,7 @@ HiveComboControl.xaml - - - RealTimeGraphControl.xaml - @@ -177,10 +175,6 @@ - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -223,16 +217,12 @@ - - {b9ae25d6-be35-492f-9079-21a7f3e6f7cc} - RealTimeGraphEx - - - {99d233c5-fee7-418e-9c25-d4584cb52e28} + + {6b9774f7-960d-438e-ad81-c6b9be328d50} RealTimeGraphX.WPF - - {6d55a3b8-46d3-493a-a143-aebd2b98d683} + + {f13a489c-80ee-4cd0-bdd4-92d959215646} RealTimeGraphX @@ -386,7 +376,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Themes/Generic.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Themes/Generic.xaml index 9cc398753..79245745e 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Themes/Generic.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Themes/Generic.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:realTimeGraphX="clr-namespace:RealTimeGraphX.WPF;assembly=RealTimeGraphX.WPF" xmlns:converters="clr-namespace:Tango.SharedUI.Converters;assembly=Tango.SharedUI" xmlns:local="clr-namespace:Tango.MachineStudio.Common.Controls"> @@ -28,4 +29,90 @@ + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj index 8d9255926..8416758fa 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj @@ -356,10 +356,6 @@ - - {b9ae25d6-be35-492f-9079-21a7f3e6f7cc} - RealTimeGraphEx - {bb2abb74-ba58-4812-83aa-ec8171f42df4} Tango.AutoComplete @@ -614,7 +610,7 @@ copy /Y "$(SolutionDir)Referenced Assemblies\Microsoft.WITDataStore32.dll" "$(Ta - + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.config b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.config new file mode 100644 index 000000000..731f6de6c --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml new file mode 100644 index 000000000..19410e824 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml.cs new file mode 100644 index 000000000..16bbca639 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace RealTimeGraphX.WPF.Demo +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml new file mode 100644 index 000000000..89cdde87c --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml @@ -0,0 +1,112 @@ + + + + + DodgerBlue + Red + Green + + + + + + + + + + + + + + + + + + + + + + + + + Duration + + + Refresh Rate + + + Minimum Y + + + Maximum Y + + + Auto Range (Y) + + Stroke + + + Thickness + + + Fill + + + Paused + + + + + + + + + + + + + + + + + Duration + + + Refresh Rate + + + Minimum Y + + + Maximum Y + + + Auto Range (Y) + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml.cs new file mode 100644 index 000000000..11bdb2d6a --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindow.xaml.cs @@ -0,0 +1,29 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace RealTimeGraphX.WPF.Demo +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + DataContext = new MainWindowVM(); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindowVM.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindowVM.cs new file mode 100644 index 000000000..390230140 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/MainWindowVM.cs @@ -0,0 +1,107 @@ +using RealTimeGraphX.DataPoints; +using RealTimeGraphX.Renderers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace RealTimeGraphX.WPF.Demo +{ + public class MainWindowVM + { + public WpfGraphController Controller { get; set; } + + public WpfGraphController MultiController { get; set; } + + public MainWindowVM() + { + Controller = new WpfGraphController(); + Controller.Range.MinimumY = 0; + Controller.Range.MaximumY = 1080; + Controller.Range.MaximumX = TimeSpan.FromSeconds(10); + Controller.Range.AutoY = true; + + Controller.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Name = "Series", + Stroke = Colors.DodgerBlue, + }); + + MultiController = new WpfGraphController(); + MultiController.Range.MinimumY = 0; + MultiController.Range.MaximumY = 1080; + MultiController.Range.MaximumX = TimeSpan.FromSeconds(10); + MultiController.Range.AutoY = true; + + MultiController.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Name = "Series 1", + Stroke = Colors.Red, + }); + + MultiController.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Name = "Series 2", + Stroke = Colors.Green, + }); + + MultiController.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Name = "Series 3", + Stroke = Colors.Blue, + }); + + MultiController.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Name = "Series 4", + Stroke = Colors.Yellow, + }); + + MultiController.DataSeriesCollection.Add(new WpfGraphDataSeries() + { + Name = "Series 5", + Stroke = Colors.Gray, + }); + + Stopwatch watch = new Stopwatch(); + watch.Start(); + + Task.Factory.StartNew(() => + { + while (true) + { + var y = System.Windows.Forms.Cursor.Position.Y; + + List yy = new List() + { + y, + y + 20, + y + 40, + y + 60, + y + 80, + }; + + var x = watch.Elapsed; + + List xx = new List() + { + x, + x, + x, + x, + x + }; + + Controller.PushData(x, y); + MultiController.PushData(xx, yy); + + Thread.Sleep(30); + } + }); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..7e504011c --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RealTimeGraphX.WPF.Demo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RealTimeGraphX.WPF.Demo")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.Designer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.Designer.cs new file mode 100644 index 000000000..b1dd1cfba --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RealTimeGraphX.WPF.Demo.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RealTimeGraphX.WPF.Demo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.resx b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.Designer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.Designer.cs new file mode 100644 index 000000000..d6fd721ce --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RealTimeGraphX.WPF.Demo.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.settings b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj new file mode 100644 index 000000000..5b430a90c --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj @@ -0,0 +1,116 @@ + + + + + Debug + AnyCPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60} + WinExe + RealTimeGraphX.WPF.Demo + RealTimeGraphX.WPF.Demo + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + MSBuild:Compile + Designer + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {6b9774f7-960d-438e-ad81-c6b9be328d50} + RealTimeGraphX.WPF + + + {61f001e1-6e17-4ca6-8f3a-8a11d7166815} + RealTimeGraphX + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml new file mode 100644 index 000000000..6980437f1 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml @@ -0,0 +1,53 @@ + + + + + diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs new file mode 100644 index 000000000..7c92e18bf --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs @@ -0,0 +1,39 @@ +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.Navigation; +using System.Windows.Shapes; + +namespace RealTimeGraphX.WPF.Demo +{ + public class WpfGraphControl : Control + { + /// + /// Gets or sets the graph controller. + /// + public IGraphController Controller + { + get { return (IGraphController)GetValue(ControllerProperty); } + set { SetValue(ControllerProperty, value); } + } + public static readonly DependencyProperty ControllerProperty = + DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphControl), new PropertyMetadata(null)); + + /// + /// Initializes the class. + /// + static WpfGraphControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphControl), new FrameworkPropertyMetadata(typeof(WpfGraphControl))); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/ExtensionMethods.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/ExtensionMethods.cs new file mode 100644 index 000000000..39f113924 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/ExtensionMethods.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX.WPF +{ + /// + /// Contains a collection of extension methods. + /// + internal static class ExtensionMethods + { + /// + /// Converts this WPF color to a GDI color. + /// + /// The color. + /// + internal static Color ToGdiColor(this System.Windows.Media.Color color) + { + return Color.FromArgb(color.A, color.R, color.G, color.B); + } + + /// + /// Converts this WPF brush to a GDI brush. + /// + /// The brush. + /// + internal static Brush ToGdiBrush(this System.Windows.Media.Brush brush) + { + if (brush.GetType() == typeof(System.Windows.Media.SolidColorBrush)) + { + return new SolidBrush((brush as System.Windows.Media.SolidColorBrush).Color.ToGdiColor()); + } + else if (brush.GetType() == typeof(System.Windows.Media.LinearGradientBrush)) + { + System.Windows.Media.LinearGradientBrush b = brush as System.Windows.Media.LinearGradientBrush; + + double angle = Math.Atan2(b.EndPoint.Y - b.StartPoint.Y, b.EndPoint.X - b.StartPoint.X) * 180 / Math.PI; + + LinearGradientBrush gradient = new LinearGradientBrush(new Rectangle(0, 0, 200, 100), Color.Black, Color.Black, (float)angle); + + ColorBlend blend = new ColorBlend(); + + List colors = new List(); + List offsets = new List(); + + foreach (var stop in b.GradientStops) + { + colors.Add(stop.Color.ToGdiColor()); + offsets.Add((float)stop.Offset); + } + + blend.Colors = colors.ToArray(); + blend.Positions = offsets.ToArray(); + + gradient.InterpolationColors = blend; + + return gradient; + } + else + { + return new LinearGradientBrush(new PointF(0, 0), new Point(200, 100), Color.Black, Color.Black); + } + } + + /// + /// Determines whether this dependency object is running in design mode. + /// + /// The object. + internal static bool IsInDesignMode(this System.Windows.DependencyObject obj) + { + return (DesignerProperties.GetIsInDesignMode(obj)); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..a08459ddd --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RealTimeGraphX.WPF")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RealTimeGraphX.WPF")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.Designer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.Designer.cs new file mode 100644 index 000000000..601c8ad1d --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RealTimeGraphX.WPF.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RealTimeGraphX.WPF.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.resx b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.Designer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.Designer.cs new file mode 100644 index 000000000..18340e385 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RealTimeGraphX.WPF.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.settings b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj new file mode 100644 index 000000000..6446fc93f --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj @@ -0,0 +1,97 @@ + + + + + Debug + AnyCPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50} + library + RealTimeGraphX.WPF + RealTimeGraphX.WPF + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + + + + + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + {61f001e1-6e17-4ca6-8f3a-8a11d7166815} + RealTimeGraphX + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Themes/Generic.xaml b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Themes/Generic.xaml new file mode 100644 index 000000000..1070736ef --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/Themes/Generic.xaml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisControl.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisControl.cs new file mode 100644 index 000000000..23b831abe --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisControl.cs @@ -0,0 +1,172 @@ +using RealTimeGraphX.EventArguments; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +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.Navigation; +using System.Windows.Shapes; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a horizontal/vertical graph axis component. + /// + /// + public class WpfGraphAxisControl : WpfGraphComponentBase + { + private ItemsControl _items_control; + + /// + /// Initializes the class. + /// + static WpfGraphAxisControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphAxisControl), new FrameworkPropertyMetadata(typeof(WpfGraphAxisControl))); + } + + /// + /// Gets or sets the control orientation. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register("Orientation", typeof(Orientation), typeof(WpfGraphAxisControl), new PropertyMetadata(Orientation.Vertical)); + + /// + /// Gets or sets the tick item template. + /// + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(WpfGraphAxisControl), new PropertyMetadata(null)); + + /// + /// Gets or sets the tick items. + /// + internal ObservableCollection Items + { + get { return (ObservableCollection)GetValue(ItemsProperty); } + set { SetValue(ItemsProperty, value); } + } + public static readonly DependencyProperty ItemsProperty = + DependencyProperty.Register("Items", typeof(ObservableCollection), typeof(WpfGraphAxisControl), new PropertyMetadata(null)); + + /// + /// Gets or sets the number of ticks to display on the control. + /// + public int Ticks + { + get { return (int)GetValue(TicksProperty); } + set { SetValue(TicksProperty, value); } + } + public static readonly DependencyProperty TicksProperty = + DependencyProperty.Register("Ticks", typeof(int), typeof(WpfGraphAxisControl), new PropertyMetadata(9, (d, e) => (d as WpfGraphAxisControl).OnTicksChanged())); + + /// + /// Gets or sets the string format which is used to format the ticks value. + /// + public String StringFormat + { + get { return (String)GetValue(StringFormatProperty); } + set { SetValue(StringFormatProperty, value); } + } + public static readonly DependencyProperty StringFormatProperty = + DependencyProperty.Register("StringFormat", typeof(String), typeof(WpfGraphAxisControl), new PropertyMetadata(null)); + + /// + /// When overridden in a derived class, is invoked whenever application code or internal processes call . + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _items_control = GetTemplateChild("PART_ItemsControl") as ItemsControl; + OnTicksChanged(); + } + + /// + /// Called when the property has changed. + /// + protected virtual void OnTicksChanged() + { + Items = new ObservableCollection(Enumerable.Range(0, Ticks).Select(x => new WpfGraphAxisTickData())); + + if (Controller != null) + { + Controller.RequestVirtualRangeChange(); + } + } + + protected override void OnControllerChanged(IGraphController oldController, IGraphController newController) + { + base.OnControllerChanged(oldController, newController); + + if (newController != null) + { + newController.RequestVirtualRangeChange(); + } + } + + /// + /// Handles the event. + /// + /// The source of the event. + /// The event arguments. + protected override void OnVirtualRangeChanged(object sender, RangeChangedEventArgs e) + { + InvokeUI(() => + { + if (Orientation == Orientation.Vertical) + { + if (Items != null) + { + var steps = e.MinimumY.CreateRange(e.MinimumY, e.MaximumY, Ticks).Reverse().ToList(); + + for (int i = 0; i < steps.Count; i++) + { + var tick_data = Items[i]; + tick_data.Data = steps[i]; + tick_data.DisplayText = tick_data.Data.ToString(StringFormat); + tick_data.IsFirst = i == 0; + tick_data.IsLast = i == steps.Count - 1; + tick_data.IsEven = i % 2 == 0; + } + } + } + else + { + if (Items != null) + { + var steps = e.MinimumX.CreateRange(e.MinimumX, e.MaximumX, Ticks).ToList(); + + for (int i = 0; i < steps.Count; i++) + { + var tick_data = Items[i]; + tick_data.Data = steps[i]; + tick_data.DisplayText = tick_data.Data.ToString(StringFormat); + tick_data.IsFirst = i == 0; + tick_data.IsLast = i == steps.Count - 1; + tick_data.IsEven = i % 2 == 0; + } + } + } + }); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisPanel.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisPanel.cs new file mode 100644 index 000000000..f10b583f4 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisPanel.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a panel that will align its children in an axis labels like arrangement. + /// + /// + public class WpfGraphAxisPanel : Grid + { + /// + /// Gets or sets the panel orientation. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register("Orientation", typeof(Orientation), typeof(WpfGraphAxisPanel), new PropertyMetadata(Orientation.Vertical)); + + /// + /// Initializes a new instance of the class. + /// + public WpfGraphAxisPanel() + { + Loaded += VerticalAxisPanel_Loaded; + } + + /// + /// Handles the Loaded event of the VerticalAxisGrid control. + /// + /// The source of the event. + /// The instance containing the event data. + private void VerticalAxisPanel_Loaded(object sender, RoutedEventArgs e) + { + UpdatePanel(); + } + + /// + /// Updates the panel. + /// + private void UpdatePanel() + { + RowDefinitions.Clear(); + ColumnDefinitions.Clear(); + + + if (Orientation == Orientation.Vertical) + { + for (int i = 0; i < InternalChildren.Count; i++) + { + FrameworkElement element = InternalChildren[i] as FrameworkElement; + + if (i < InternalChildren.Count - 1) + { + RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); + Grid.SetRow(element, i); + element.VerticalAlignment = VerticalAlignment.Top; + + element.SizeChanged += (_, __) => + { + element.Margin = new Thickness(0, (element.ActualHeight / 2) * -1, 0, 0); + }; + } + else + { + Grid.SetRow(element, i); + element.VerticalAlignment = VerticalAlignment.Bottom; + + element.SizeChanged += (_, __) => + { + element.Margin = new Thickness(0, 0, 0, (element.ActualHeight / 2) * -1); + }; + } + } + } + else + { + for (int i = 0; i < InternalChildren.Count; i++) + { + FrameworkElement element = InternalChildren[i] as FrameworkElement; + + if (i < InternalChildren.Count - 1) + { + ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + Grid.SetColumn(element, i); + element.HorizontalAlignment = HorizontalAlignment.Left; + + element.SizeChanged += (_, __) => + { + element.Margin = new Thickness((element.ActualWidth / 2) * -1, 0, 0, 0); + }; + } + else + { + Grid.SetColumn(element, i); + element.HorizontalAlignment = HorizontalAlignment.Right; + + element.SizeChanged += (_, __) => + { + element.Margin = new Thickness(0, 0, (element.ActualWidth / 2) * -1, 0); + }; + } + } + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisTickData.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisTickData.cs new file mode 100644 index 000000000..08c0ae2fa --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphAxisTickData.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a graph axis data point tick value wrapper. + /// + public class WpfGraphAxisTickData : DependencyObject + { + /// + /// Gets or sets a value indicating whether this tick is the first tick. + /// + public bool IsFirst + { + get { return (bool)GetValue(IsFirstProperty); } + set { SetValue(IsFirstProperty, value); } + } + public static readonly DependencyProperty IsFirstProperty = + DependencyProperty.Register("IsFirst", typeof(bool), typeof(WpfGraphAxisTickData), new PropertyMetadata(false)); + + /// + /// Gets or sets a value indicating whether this tick is the last tick. + /// + public bool IsLast + { + get { return (bool)GetValue(IsLastProperty); } + set { SetValue(IsLastProperty, value); } + } + public static readonly DependencyProperty IsLastProperty = + DependencyProperty.Register("IsLast", typeof(bool), typeof(WpfGraphAxisTickData), new PropertyMetadata(false)); + + /// + /// Gets or sets a value indicating whether this tick is not the first or last. + /// + public bool IsCenter + { + get { return !IsFirst && !IsLast; } + } + + /// + /// Gets or sets a value indicating whether this tick index is even. + /// + public bool IsEven + { + get { return (bool)GetValue(IsEvenProperty); } + set { SetValue(IsEvenProperty, value); } + } + public static readonly DependencyProperty IsEvenProperty = + DependencyProperty.Register("IsEven", typeof(bool), typeof(WpfGraphAxisTickData), new PropertyMetadata(false)); + + /// + /// Gets a value indicating whether this tick index is odd. + /// + public bool IsOdd + { + get { return !IsEven; } + } + + /// + /// Gets or sets the actual graph data point. + /// + public IGraphDataPoint Data + { + get { return (IGraphDataPoint)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + public static readonly DependencyProperty DataProperty = + DependencyProperty.Register("Data", typeof(IGraphDataPoint), typeof(WpfGraphAxisTickData), new PropertyMetadata(null)); + + /// + /// Gets or sets the display text. + /// + public String DisplayText + { + get { return (String)GetValue(DisplayTextProperty); } + set { SetValue(DisplayTextProperty, value); } + } + public static readonly DependencyProperty DisplayTextProperty = + DependencyProperty.Register("DisplayText", typeof(String), typeof(WpfGraphAxisTickData), new PropertyMetadata(null)); + + /// + /// Gets the Data Point value. + /// + public object Value + { + get + { + return Data != null ? Data.GetValue() : null; + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphComponentBase.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphComponentBase.cs new file mode 100644 index 000000000..e48c18678 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphComponentBase.cs @@ -0,0 +1,66 @@ +using RealTimeGraphX.EventArguments; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a graph component base class. + /// + /// + public abstract class WpfGraphComponentBase : Control + { + /// + /// Gets or sets the graph controller. + /// + public IGraphController Controller + { + get { return (IGraphController)GetValue(ControllerProperty); } + set { SetValue(ControllerProperty, value); } + } + public static readonly DependencyProperty ControllerProperty = + DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphComponentBase), new PropertyMetadata(null, (d, e) => (d as WpfGraphComponentBase).OnControllerChanged(e.OldValue as IGraphController, e.NewValue as IGraphController))); + + /// + /// Called when the controller has changed. + /// + /// The old controller. + /// The new controller. + protected virtual void OnControllerChanged(IGraphController oldController, IGraphController newController) + { + if (oldController != null) + { + oldController.VirtualRangeChanged -= OnVirtualRangeChanged; + } + + if (newController != null) + { + newController.VirtualRangeChanged += OnVirtualRangeChanged; + } + } + + /// + /// Handles the event. + /// + /// The source of the event. + /// The event arguments. + protected virtual void OnVirtualRangeChanged(object sender, RangeChangedEventArgs e) + { + //Optional + } + + /// + /// Invokes the specified method on the component dispatcher. + /// + /// The action. + protected void InvokeUI(Action action) + { + Dispatcher.BeginInvoke(action); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphController.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphController.cs new file mode 100644 index 000000000..6067a7e50 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphController.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a WPF graph controller. + /// + /// The type of the x data point. + /// The type of the y data point. + /// + public class WpfGraphController : GraphController + where TXDataPoint : GraphDataPoint + where TYDataPoint : GraphDataPoint + { + + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphDataSeries.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphDataSeries.cs new file mode 100644 index 000000000..15a99ab54 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphDataSeries.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a WPF data series. + /// + /// + /// + public class WpfGraphDataSeries : GraphObject, IGraphDataSeries + { + #region Internal Properties + + /// + /// Gets the GDI stroke color. + /// + internal System.Drawing.Color GdiStroke { get; private set; } + + /// + /// Gets the GDI fill brush. + /// + internal System.Drawing.Brush GdiFill { get; private set; } + + /// + /// Gets or sets the GDI pen. + /// + internal System.Drawing.Pen GdiPen { get; set; } + + #endregion + + private String _name; + /// + /// Gets or sets the series name. + /// + public String Name + { + get { return _name; } + set { _name = value; RaisePropertyChangedAuto(); } + } + + private float _strokeThickness; + /// + /// Gets or sets the stroke thickness. + /// + public float StrokeThickness + { + get + { + return _strokeThickness; + } + set + { + _strokeThickness = value; + GdiPen = new System.Drawing.Pen(GdiStroke, _strokeThickness); + RaisePropertyChangedAuto(); + } + } + + private bool _isVisible; + /// + /// Gets or sets a value indicating whether this series should be visible. + /// + public bool IsVisible + { + get { return _isVisible; } + set { _isVisible = value; RaisePropertyChangedAuto(); } + } + + private Color _stroke; + /// + /// Gets or sets the series stroke color. + /// + public Color Stroke + { + get { return _stroke; } + set + { + _stroke = value; + RaisePropertyChangedAuto(); + + if (_stroke != null) + { + GdiStroke = _stroke.ToGdiColor(); + GdiPen = new System.Drawing.Pen(GdiStroke, StrokeThickness); + } + else + { + GdiStroke = System.Drawing.Color.Transparent; + } + } + } + + private Brush _fill; + /// + /// Gets or sets the series fill brush. + /// + public Brush Fill + { + get { return _fill; } + set + { + _fill = value; + RaisePropertyChangedAuto(); + + if (_fill != null) + { + GdiFill = _fill.ToGdiBrush(); + } + else + { + GdiFill = null; + } + } + } + + /// + /// Initializes a new instance of the class. + /// + public WpfGraphDataSeries() + { + StrokeThickness = 1; + IsVisible = true; + Stroke = Colors.DodgerBlue; + } + + /// + /// Gets or sets a value indicating whether to fill the series. + /// + public bool UseFill + { + get { return Fill != null; } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphGridLines.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphGridLines.cs new file mode 100644 index 000000000..3eb25f3d8 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphGridLines.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a graph grid lines component. + /// + /// + public class WpfGraphGridLines : WpfGraphComponentBase + { + /// + /// Gets or sets the number of grid rows. + /// + public int Rows + { + get { return (int)GetValue(RowsProperty); } + set { SetValue(RowsProperty, value); } + } + public static readonly DependencyProperty RowsProperty = + DependencyProperty.Register("Rows", typeof(int), typeof(WpfGraphGridLines), new PropertyMetadata(8, (d, e) => (d as WpfGraphGridLines).UpdateGridLines())); + + /// + /// Gets or sets the number of grid columns. + /// + public int Columns + { + get { return (int)GetValue(ColumnsProperty); } + set { SetValue(ColumnsProperty, value); } + } + public static readonly DependencyProperty ColumnsProperty = + DependencyProperty.Register("Columns", typeof(int), typeof(WpfGraphGridLines), new PropertyMetadata(8, (d, e) => (d as WpfGraphGridLines).UpdateGridLines())); + + /// + /// Gets or sets the vertical items. + /// + internal IEnumerable VerticalItems + { + get { return (IEnumerable)GetValue(VerticalItemsProperty); } + set { SetValue(VerticalItemsProperty, value); } + } + internal static readonly DependencyProperty VerticalItemsProperty = + DependencyProperty.Register("VerticalItems", typeof(IEnumerable), typeof(WpfGraphGridLines), new PropertyMetadata(null)); + + /// + /// Gets or sets the horizontal items. + /// + internal IEnumerable HorizontalItems + { + get { return (IEnumerable)GetValue(HorizontalItemsProperty); } + set { SetValue(HorizontalItemsProperty, value); } + } + internal static readonly DependencyProperty HorizontalItemsProperty = + DependencyProperty.Register("HorizontalItems", typeof(IEnumerable), typeof(WpfGraphGridLines), new PropertyMetadata(null)); + + /// + /// Initializes the class. + /// + static WpfGraphGridLines() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphGridLines), new FrameworkPropertyMetadata(typeof(WpfGraphGridLines))); + } + + /// + /// Initializes a new instance of the class. + /// + public WpfGraphGridLines() + { + Loaded += GridLines_Loaded; + } + + /// + /// Handles the Loaded event of the GridLines control. + /// + /// The source of the event. + /// The instance containing the event data. + private void GridLines_Loaded(object sender, RoutedEventArgs e) + { + UpdateGridLines(); + } + + /// + /// Updates the grid lines. + /// + private void UpdateGridLines() + { + VerticalItems = Enumerable.Range(0, Rows).ToList(); + HorizontalItems = Enumerable.Range(0, Columns).ToList(); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphSurface.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphSurface.cs new file mode 100644 index 000000000..ebcff3472 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX.WPF/WpfGraphSurface.cs @@ -0,0 +1,380 @@ +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.Navigation; +using System.Windows.Shapes; +using RealTimeGraphX.EventArguments; + +namespace RealTimeGraphX.WPF +{ + /// + /// Represents a WPF graph surface. + /// + /// + /// + public class WpfGraphSurface : Control, IGraphSurface + { + private WriteableBitmap _writeable_bitmap; + private System.Drawing.Bitmap _gdi_bitmap; + private System.Drawing.Graphics _g; + + private bool _size_changed; + private System.Drawing.SizeF _size; + private System.Drawing.RectangleF _zoom_rect; + private Rectangle _selection_rectangle; + private Canvas _selection_canvas; + private bool _is_selection_mouse_down_zoom; + private bool _is_selection_mouse_down_pan; + private Point _selection_start_point; + private bool _is_scaled; + private Point _current_mouse_position; + private Point _last_mouse_position; + private Grid _grid; + + #region Properties + + /// + /// Gets or sets current graph rendered image. + /// + public BitmapSource Image + { + get { return (BitmapSource)GetValue(ImageProperty); } + private set { SetValue(ImageProperty, value); } + } + public static readonly DependencyProperty ImageProperty = + DependencyProperty.Register("Image", typeof(BitmapSource), typeof(WpfGraphSurface), new PropertyMetadata(null)); + + /// + /// Gets or sets the graph controller. + /// + public IGraphController Controller + { + get { return (IGraphController)GetValue(ControllerProperty); } + set { SetValue(ControllerProperty, value); } + } + public static readonly DependencyProperty ControllerProperty = + DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphSurface), new PropertyMetadata(null, (d, e) => (d as WpfGraphSurface).OnControllerChanged(e.OldValue as IGraphController, e.NewValue as IGraphController))); + + #endregion + + #region Constructors + + /// + /// Initializes the class. + /// + static WpfGraphSurface() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphSurface), new FrameworkPropertyMetadata(typeof(WpfGraphSurface))); + } + + /// + /// Initializes a new instance of the class. + /// + public WpfGraphSurface() + { + SizeChanged += WpfGraphSurface_SizeChanged; + } + + #endregion + + #region Apply Template + + /// + /// When overridden in a derived class, is invoked whenever application code or internal processes call . + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _selection_rectangle = GetTemplateChild("PART_SelectionRectangle") as Rectangle; + _selection_canvas = GetTemplateChild("PART_SelectionCanvas") as Canvas; + _grid = GetTemplateChild("PART_Grid") as Grid; + + _selection_canvas.MouseDown += OnSelectionCanvasMouseDown; + _selection_canvas.MouseUp += OnSelectionCanvasMouseUp; + _selection_canvas.MouseMove += OnSelectionCanvasMouseMove; + } + + #endregion + + #region Protected Methods + + /// + /// Called when the mouse moves over the zoom/pan selection canvas. + /// + /// The sender. + /// The instance containing the event data. + protected virtual void OnSelectionCanvasMouseMove(object sender, MouseEventArgs e) + { + _current_mouse_position = e.GetPosition(_selection_canvas); + + if (_is_selection_mouse_down_zoom && Keyboard.IsKeyDown(Key.LeftCtrl)) + { + Canvas.SetLeft(_selection_rectangle, _selection_start_point.X); + Canvas.SetTop(_selection_rectangle, _selection_start_point.Y); + + Point _selection_current_point = e.GetPosition(_selection_canvas); + + if (_selection_current_point.X - _selection_start_point.X > 1) + { + _selection_rectangle.Width = _selection_current_point.X - _selection_start_point.X; + } + + if (_selection_current_point.Y - _selection_start_point.Y > 1) + { + _selection_rectangle.Height = _selection_current_point.Y - _selection_start_point.Y; + } + + if (_selection_current_point.X < _selection_start_point.X) + { + Canvas.SetLeft(_selection_rectangle, _selection_current_point.X); + _selection_rectangle.Width = _selection_start_point.X - _selection_current_point.X; + } + + if (_selection_current_point.Y < _selection_start_point.Y) + { + Canvas.SetTop(_selection_rectangle, _selection_current_point.Y); + _selection_rectangle.Height = _selection_start_point.Y - _selection_current_point.Y; + } + } + else if (_is_selection_mouse_down_pan && _is_scaled) + { + Point _selection_current_point = e.GetPosition(_selection_canvas); + + double delta_x = _current_mouse_position.X - _last_mouse_position.X; + double delta_y = _current_mouse_position.Y - _last_mouse_position.Y; + + double x = _zoom_rect.Left - delta_x; + double y = _zoom_rect.Top - delta_y; + + if (x < 0) + { + x = 0; + } + + if (y < 0) + { + y = 0; + } + + if (x + _zoom_rect.Width > _size.Width) + { + x = x - (x + _zoom_rect.Width - _size.Width); + } + + if (y + _zoom_rect.Height > _size.Height) + { + y = y - (y + _zoom_rect.Height - _size.Height); + } + + _zoom_rect = new System.Drawing.RectangleF((float)x, (float)y, _zoom_rect.Width, _zoom_rect.Height); + } + + _last_mouse_position = _current_mouse_position; + } + + /// + /// Called when the mouse released from the zoom/pan selection canvas. + /// + /// The sender. + /// The instance containing the event data. + protected virtual void OnSelectionCanvasMouseUp(object sender, MouseButtonEventArgs e) + { + _selection_canvas.ReleaseMouseCapture(); + + if (_is_selection_mouse_down_pan) + { + _is_selection_mouse_down_pan = false; + } + else if (_is_selection_mouse_down_zoom) + { + _is_selection_mouse_down_zoom = false; + + _zoom_rect = new System.Drawing.RectangleF((float)Canvas.GetLeft(_selection_rectangle), (float)Canvas.GetTop(_selection_rectangle), (float)_selection_rectangle.Width, (float)_selection_rectangle.Height); + _selection_rectangle.Visibility = Visibility.Hidden; + _is_scaled = true; + } + } + + /// + /// Called when the mouse pressed on the zoom/pan selection canvas. + /// + /// The sender. + /// The instance containing the event data. + protected virtual void OnSelectionCanvasMouseDown(object sender, MouseButtonEventArgs e) + { + Mouse.Capture(_selection_canvas); + + _selection_start_point = e.GetPosition(_selection_canvas); + _current_mouse_position = _selection_start_point; + _last_mouse_position = _current_mouse_position; + + if (e.ClickCount == 2) + { + _zoom_rect = new System.Drawing.RectangleF(); + _is_scaled = false; + } + else if (Keyboard.IsKeyDown(Key.LeftCtrl)) + { + _selection_rectangle.Width = 0; + _selection_rectangle.Height = 0; + _is_selection_mouse_down_zoom = true; + _is_selection_mouse_down_pan = false; + _selection_rectangle.Visibility = Visibility.Visible; + } + else + { + _is_selection_mouse_down_pan = true; + } + } + + /// + /// Called when the property has changed. + /// + /// The old controller. + /// The new controller. + protected virtual void OnControllerChanged(IGraphController oldController, IGraphController newController) + { + if (oldController != null) + { + oldController.Surface = null; + } + + if (newController != null) + { + newController.Surface = this; + } + } + + #endregion + + #region IGraphSurface + + /// + /// Called before drawing has started. + /// + public void BeginDraw() + { + if (_size_changed) + { + _writeable_bitmap = new WriteableBitmap((int)Math.Max(_size.Width, 1), (int)Math.Max(_size.Height, 1), 96.0, 96.0, PixelFormats.Pbgra32, null); + + _gdi_bitmap = new System.Drawing.Bitmap(_writeable_bitmap.PixelWidth, _writeable_bitmap.PixelHeight, + _writeable_bitmap.BackBufferStride, + System.Drawing.Imaging.PixelFormat.Format32bppPArgb, + _writeable_bitmap.BackBuffer); + + _size_changed = false; + } + + _writeable_bitmap.Lock(); + + _g = System.Drawing.Graphics.FromImage(_gdi_bitmap); + _g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + + _g.Clear(System.Drawing.Color.Transparent); + } + + /// + /// Applies transformation on the current pass. + /// + /// The transform. + public void SetTransform(GraphTransform transform) + { + _g.TranslateTransform((float)transform.TranslateX, (float)transform.TranslateY); + _g.ScaleTransform((float)transform.ScaleX, (float)transform.ScaleY); + } + + /// + /// Draws the stroke of the specified data series points. + /// + /// The data series. + /// The points. + public void DrawSeries(WpfGraphDataSeries dataSeries, IEnumerable points) + { + _g.DrawCurve(dataSeries.GdiPen, points.ToArray()); + } + + /// + /// Fills the specified data series points. + /// + /// The data series. + /// The points. + public void FillSeries(WpfGraphDataSeries dataSeries, IEnumerable points) + { + var brush = dataSeries.GdiFill; + + if (dataSeries.GdiFill is System.Drawing.Drawing2D.LinearGradientBrush) + { + var gradient = dataSeries.GdiFill as System.Drawing.Drawing2D.LinearGradientBrush; + gradient.ResetTransform(); + gradient.ScaleTransform((_size.Width / gradient.Rectangle.Width), (_size.Height / gradient.Rectangle.Height)); + } + + _g.FillPolygon(dataSeries.GdiFill, points.ToArray()); + } + + /// + /// Called when drawing has completed. + /// + public void EndDraw() + { + _writeable_bitmap.AddDirtyRect(new Int32Rect(0, 0, _writeable_bitmap.PixelWidth, _writeable_bitmap.PixelHeight)); + _writeable_bitmap.Unlock(); + + var cloned = _writeable_bitmap.Clone(); + cloned.Freeze(); + + Dispatcher.BeginInvoke(new Action((() => + { + Image = cloned; + }))); + + _g.Dispose(); + } + + /// + /// Returns the actual size of the surface. + /// + /// + public System.Drawing.SizeF GetSize() + { + return _size; + } + + /// + /// Returns the current bounds of the zooming rectangle. + /// + /// + public System.Drawing.RectangleF GetZoomRect() + { + return _zoom_rect; + } + + #endregion + + #region Event Handlers + + /// + /// Handles the WPF Graph Surface Size Changed event. + /// + /// The source of the event. + /// The event arguments. + private void WpfGraphSurface_SizeChanged(object sender, SizeChangedEventArgs e) + { + _size = new System.Drawing.SizeF((float)e.NewSize.Width, (float)e.NewSize.Height); + _size_changed = true; + } + + #endregion + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/DoubleDataPoint.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/DoubleDataPoint.cs new file mode 100644 index 000000000..b4740fcfa --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/DoubleDataPoint.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX.DataPoints +{ + /// + /// Represents a graph value data point. + /// + /// + public class DoubleDataPoint : GraphDataPoint + { + /// + /// Initializes a new instance of the class. + /// + public DoubleDataPoint() : base() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public DoubleDataPoint(double value) : base(value) + { + + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value. + /// + /// The result of the conversion. + /// + public static implicit operator DoubleDataPoint(double value) + { + return new DoubleDataPoint(value); + } + + /// + /// Implements the operator -. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static DoubleDataPoint operator -(DoubleDataPoint a, DoubleDataPoint b) + { + return new DoubleDataPoint(a.Value - b.Value); + } + + /// + /// Implements the operator +. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static DoubleDataPoint operator +(DoubleDataPoint a, DoubleDataPoint b) + { + return new DoubleDataPoint(a.Value + b.Value); + } + + /// + /// Sums the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Add(IGraphDataPoint other) + { + return new DoubleDataPoint(this.Value + (other as DoubleDataPoint).Value); + } + + /// + /// Subtract the value of another instance from this instance and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Subtract(IGraphDataPoint other) + { + return new DoubleDataPoint(this.Value - (other as DoubleDataPoint).Value); + } + + /// + /// Multiplies the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Multiply(IGraphDataPoint other) + { + return new DoubleDataPoint(this.Value * (other as DoubleDataPoint).Value); + } + + /// + /// Divides the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Divide(IGraphDataPoint other) + { + return new DoubleDataPoint(this.Value / (other as DoubleDataPoint).Value); + } + + /// + /// Returns the percentage value of this instance between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// + public override double ComputeRelativePosition(IGraphDataPoint min, IGraphDataPoint max) + { + DoubleDataPoint dMin = min as DoubleDataPoint; + DoubleDataPoint dMax = max as DoubleDataPoint; + + var result = ((Value - dMin) * 100) / (dMax - dMin); + + return double.IsNaN(result) || double.IsInfinity(result) ? dMin.Value : result; + } + + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + public override IGraphDataPoint ComputeAbsolutePosition(IGraphDataPoint min, IGraphDataPoint max, double percentage) + { + double minimum = (double)min.GetValue(); + double maximum = (double)max.GetValue(); + + return new DoubleDataPoint(minimum + (maximum - minimum) * percentage); + } + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + public override IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count) + { + double minimum = (double)min.GetValue(); + double maximum = (double)max.GetValue(); + + return Enumerable.Range(0, count). + Select(i => minimum + (maximum - minimum) * ((double)i / (count - 1))). + Select(x => new DoubleDataPoint(x)); + } + + /// + /// Returns a formated string of this data point. + /// + /// The format. + /// + public override string ToString(string format) + { + return Value.ToString(format); + } + + /// + /// Parses the specified value and returns a new instance of data point. + /// + /// The value. + /// + public override IGraphDataPoint Parse(string value) + { + return new DoubleDataPoint(double.Parse(value)); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/FloatDataPoint.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/FloatDataPoint.cs new file mode 100644 index 000000000..6e225a679 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/FloatDataPoint.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX.DataPoints +{ + /// + /// Represents a graph value data point. + /// + /// + public class FloatDataPoint : GraphDataPoint + { + /// + /// Initializes a new instance of the class. + /// + public FloatDataPoint() : base() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public FloatDataPoint(float value) : base(value) + { + + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value. + /// + /// The result of the conversion. + /// + public static implicit operator FloatDataPoint(float value) + { + return new FloatDataPoint(value); + } + + /// + /// Implements the operator -. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static FloatDataPoint operator -(FloatDataPoint a, FloatDataPoint b) + { + return new FloatDataPoint(a.Value - b.Value); + } + + /// + /// Implements the operator +. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static FloatDataPoint operator +(FloatDataPoint a, FloatDataPoint b) + { + return new FloatDataPoint(a.Value + b.Value); + } + + /// + /// Sums the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Add(IGraphDataPoint other) + { + return new FloatDataPoint(this.Value + (other as FloatDataPoint).Value); + } + + /// + /// Subtract the value of another instance from this instance and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Subtract(IGraphDataPoint other) + { + return new FloatDataPoint(this.Value - (other as FloatDataPoint).Value); + } + + /// + /// Multiplies the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Multiply(IGraphDataPoint other) + { + return new FloatDataPoint(this.Value * (other as FloatDataPoint).Value); + } + + /// + /// Divides the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Divide(IGraphDataPoint other) + { + return new FloatDataPoint(this.Value / (other as FloatDataPoint).Value); + } + + /// + /// Returns the percentage value of this instance between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// + public override double ComputeRelativePosition(IGraphDataPoint min, IGraphDataPoint max) + { + FloatDataPoint dMin = min as FloatDataPoint; + FloatDataPoint dMax = max as FloatDataPoint; + + var result = ((Value - dMin) * 100) / (dMax - dMin); + + return double.IsNaN(result) ? dMin.Value : result; + } + + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + public override IGraphDataPoint ComputeAbsolutePosition(IGraphDataPoint min, IGraphDataPoint max, double percentage) + { + double minimum = (double)min.GetValue(); + double maximum = (double)max.GetValue(); + + return new FloatDataPoint((float)(minimum + (maximum - minimum) * percentage)); + } + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + public override IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count) + { + double minimum = (double)min.GetValue(); + double maximum = (double)max.GetValue(); + + return Enumerable.Range(0, count). + Select(i => minimum + (maximum - minimum) * ((double)i / (count - 1))). + Select(x => new FloatDataPoint((float)x)); + } + + /// + /// Returns a formated string of this data point. + /// + /// The format. + /// + public override string ToString(string format) + { + return Value.ToString(format); + } + + /// + /// Parses the specified value and returns a new instance of data point. + /// + /// The value. + /// + public override IGraphDataPoint Parse(string value) + { + return new FloatDataPoint(float.Parse(value)); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/Int32DataPoint.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/Int32DataPoint.cs new file mode 100644 index 000000000..4bccdcb74 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/Int32DataPoint.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX.DataPoints +{ + /// + /// Represents a graph value data point. + /// + /// + public class Int32DataPoint : GraphDataPoint + { + /// + /// Initializes a new instance of the class. + /// + public Int32DataPoint() : base() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public Int32DataPoint(int value) : base(value) + { + + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value. + /// + /// The result of the conversion. + /// + public static implicit operator Int32DataPoint(int value) + { + return new Int32DataPoint(value); + } + + /// + /// Implements the operator -. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static Int32DataPoint operator -(Int32DataPoint a, Int32DataPoint b) + { + return new Int32DataPoint(a.Value - b.Value); + } + + /// + /// Implements the operator +. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static Int32DataPoint operator +(Int32DataPoint a, Int32DataPoint b) + { + return new Int32DataPoint(a.Value + b.Value); + } + + /// + /// Sums the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Add(IGraphDataPoint other) + { + return new Int32DataPoint(this.Value + (other as Int32DataPoint).Value); + } + + /// + /// Subtract the value of another instance from this instance and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Subtract(IGraphDataPoint other) + { + return new Int32DataPoint(this.Value - (other as Int32DataPoint).Value); + } + + /// + /// Multiplies the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Multiply(IGraphDataPoint other) + { + return new Int32DataPoint(this.Value * (other as Int32DataPoint).Value); + } + + /// + /// Divides the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Divide(IGraphDataPoint other) + { + return new Int32DataPoint(this.Value / (other as Int32DataPoint).Value); + } + + /// + /// Returns the percentage value of this instance between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// + public override double ComputeRelativePosition(IGraphDataPoint min, IGraphDataPoint max) + { + Int32DataPoint dMin = min as Int32DataPoint; + Int32DataPoint dMax = max as Int32DataPoint; + + var result = ((Value - dMin) * 100) / (dMax - dMin); + + return result; + } + + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + public override IGraphDataPoint ComputeAbsolutePosition(IGraphDataPoint min, IGraphDataPoint max, double percentage) + { + int minimum = (int)min.GetValue(); + int maximum = (int)max.GetValue(); + + return new Int32DataPoint((int)(minimum + (maximum - minimum) * percentage)); + } + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + public override IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count) + { + int minimum = (int)min.GetValue(); + int maximum = (int)max.GetValue(); + + return Enumerable.Range(0, count). + Select(i => minimum + (maximum - minimum) * ((int)i / (count - 1))). + Select(x => new Int32DataPoint(x)); + } + + /// + /// Returns a formated string of this data point. + /// + /// The format. + /// + public override string ToString(string format) + { + return Value.ToString(format); + } + + /// + /// Parses the specified value and returns a new instance of data point. + /// + /// The value. + /// + public override IGraphDataPoint Parse(string value) + { + return new Int32DataPoint(int.Parse(value)); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs new file mode 100644 index 000000000..737c93c81 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX.DataPoints +{ + /// + /// Represents a graph value data point. + /// + /// + public class TimeSpanDataPoint : GraphDataPoint + { + /// + /// Initializes a new instance of the class. + /// + public TimeSpanDataPoint() : base() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public TimeSpanDataPoint(TimeSpan value) : base(value) + { + + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value. + /// + /// The result of the conversion. + /// + public static implicit operator TimeSpanDataPoint(TimeSpan value) + { + return new TimeSpanDataPoint(value); + } + + /// + /// Implements the operator -. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static TimeSpanDataPoint operator -(TimeSpanDataPoint a, TimeSpanDataPoint b) + { + return new TimeSpanDataPoint(a.Value - b.Value); + } + + /// + /// Implements the operator +. + /// + /// a. + /// The b. + /// + /// The result of the operator. + /// + public static TimeSpanDataPoint operator +(TimeSpanDataPoint a, TimeSpanDataPoint b) + { + return new TimeSpanDataPoint(a.Value + b.Value); + } + + /// + /// Sums the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Add(IGraphDataPoint other) + { + return new TimeSpanDataPoint(this.Value + (other as TimeSpanDataPoint).Value); + } + + /// + /// Subtract the value of another instance from this instance and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Subtract(IGraphDataPoint other) + { + return new TimeSpanDataPoint(this.Value - (other as TimeSpanDataPoint).Value); + } + + /// + /// Multiplies the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Multiply(IGraphDataPoint other) + { + return new TimeSpanDataPoint(TimeSpan.FromMilliseconds(this.Value.TotalMilliseconds * (other as TimeSpanDataPoint).Value.TotalMilliseconds)); + } + + /// + /// Divides the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public override IGraphDataPoint Divide(IGraphDataPoint other) + { + return new TimeSpanDataPoint(TimeSpan.FromMilliseconds(this.Value.TotalMilliseconds / (other as TimeSpanDataPoint).Value.TotalMilliseconds)); + } + + /// + /// Returns the percentage value of this instance between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// + public override double ComputeRelativePosition(IGraphDataPoint min, IGraphDataPoint max) + { + TimeSpan dMin = min as TimeSpanDataPoint; + TimeSpan dMax = max as TimeSpanDataPoint; + + var result = ((Value.TotalMilliseconds - dMin.TotalMilliseconds) * 100) / (dMax.TotalMilliseconds - dMin.TotalMilliseconds); + + return double.IsNaN(result) ? dMin.TotalMilliseconds : result; + } + + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + public override IGraphDataPoint ComputeAbsolutePosition(IGraphDataPoint min, IGraphDataPoint max, double percentage) + { + double minimum = ((TimeSpan)min.GetValue()).TotalMilliseconds; + double maximum = ((TimeSpan)max.GetValue()).TotalMilliseconds; + + return new TimeSpanDataPoint(TimeSpan.FromMilliseconds(minimum + (maximum - minimum) * percentage)); + } + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + public override IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count) + { + double minimum = ((TimeSpan)min.GetValue()).TotalMilliseconds; + double maximum = ((TimeSpan)max.GetValue()).TotalMilliseconds; + + return Enumerable.Range(0, count). + Select(i => minimum + (maximum - minimum) * ((double)i / (count - 1))). + Select(x => new TimeSpanDataPoint(TimeSpan.FromMilliseconds(x))); + } + + /// + /// Returns a formated string of this data point. + /// + /// The format. + /// + public override string ToString(string format) + { + return Value.ToString(format); + } + + /// + /// Parses the specified value and returns a new instance of data point. + /// + /// The value. + /// + public override IGraphDataPoint Parse(string value) + { + return new TimeSpanDataPoint(TimeSpan.Parse(value)); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/EventArguments/RangeChangedEventArgs.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/EventArguments/RangeChangedEventArgs.cs new file mode 100644 index 000000000..15d5bb7ba --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/EventArguments/RangeChangedEventArgs.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX.EventArguments +{ + /// + /// Represents a graph range change event arguments. + /// + /// + public class RangeChangedEventArgs : EventArgs + { + /// + /// Gets or sets the minimum x value. + /// + public GraphDataPoint MinimumX { get; set; } + + /// + /// Gets or sets the maximum x value. + /// + public GraphDataPoint MaximumX { get; set; } + + /// + /// Gets or sets the minimum y value. + /// + public GraphDataPoint MinimumY { get; set; } + + /// + /// Gets or sets the maximum y value. + /// + public GraphDataPoint MaximumY { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public RangeChangedEventArgs() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The minimum x value. + /// The maximum x value. + /// The minimum y value. + /// The maximum y value. + public RangeChangedEventArgs(GraphDataPoint minimumX, GraphDataPoint maximumX,GraphDataPoint minimumY,GraphDataPoint maximumY) : this() + { + MinimumX = minimumX; + MaximumX = maximumX; + MinimumY = minimumY; + MaximumY = maximumY; + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphCommand.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphCommand.cs new file mode 100644 index 000000000..e16ecd675 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphCommand.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph relay command. + /// + /// + public class GraphCommand : ICommand + { + private Action _action; + private Func _canExecute; + + /// + /// Initializes a new instance of the class. + /// + /// The action. + /// The can execute. + public GraphCommand(Action action, Func canExecute) + { + _action = action; + _canExecute = canExecute; + } + + /// + /// Initializes a new instance of the class. + /// + /// The action. + public GraphCommand(Action action) : this(action, null) + { + + } + + /// + /// Defines the method that determines whether the command can execute in its current state. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// + /// true if this command can be executed; otherwise, false. + /// + public bool CanExecute(object parameter) + { + if (_canExecute != null) + { + return _canExecute(); + } + + return true; + } + + /// + /// Defines the method to be called when the command is invoked. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + public void Execute(object parameter) + { + _action(); + } + + /// + /// Raises the can execute changed event. + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, new EventArgs()); + } + + /// + /// Occurs when changes occur that affect whether or not the command should execute. + /// + /// + public event EventHandler CanExecuteChanged; + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphController.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphController.cs new file mode 100644 index 000000000..9aabc649a --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphController.cs @@ -0,0 +1,543 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using RealTimeGraphX.EventArguments; +using RealTimeGraphX.Renderers; + +namespace RealTimeGraphX +{ + /// + /// Represents an base class. + /// + /// The type of the data series. + /// The type of the x data point. + /// The type of the y data point. + /// + /// + public abstract class GraphController : GraphObject, IGraphController + where TXDataPoint : GraphDataPoint + where TYDataPoint : GraphDataPoint + where TDataSeries : IGraphDataSeries + { + private GraphDataQueue> _pending_series_collection; + private Dictionary _to_render; + private DateTime _last_render_time; + private object _render_lock = new object(); + private Thread _render_thread; + + #region Pending Series Class + + protected class PendingSeries + { + public TDataSeries Series { get; set; } + public List XX { get; set; } + public List YY { get; set; } + + public int NewItemsCount + { + get { return XX.Count - RenderedItems; } + } + + public int RenderedItems { get; set; } + + public bool IsClearSeries { get; set; } + } + + #endregion + + #region Events + + /// + /// Occurs when the current effective minimum/maximum has changed. + /// + public event EventHandler EffectiveRangeChanged; + + /// + /// Occurs when the current virtual (effective minimum/maximum after transformation) minimum/maximum has changed. + /// + public event EventHandler VirtualRangeChanged; + + #endregion + + #region Properties + + /// + /// Gets or sets the controller refresh rate. + /// Higher rate requires more CPU time. + /// + public TimeSpan RefreshRate { get; set; } + + /// + /// Gets or sets a value indicating whether to pause rendering. + /// + public bool IsPaused { get; set; } + + /// + /// Gets the data series collection. + /// + public ObservableCollection DataSeriesCollection { get; } + + private IGraphRenderer _renderer; + /// + /// Gets or sets the graph renderer. + /// + public IGraphRenderer Renderer + { + get + { + return _renderer; + } + set + { + _renderer = value; RaisePropertyChangedAuto(); + } + } + + private IGraphSurface _surface; + /// + /// Gets or sets the rendering surface. + /// + public IGraphSurface Surface + { + get { return _surface; } + set + { + _surface = value; + RequestVirtualRangeChange(); + } + } + + private GraphRange _range; + /// + /// Gets or sets the graph range (data point boundaries). + /// + public GraphRange Range + { + get + { + return _range; + } + set + { + _range = value; RaisePropertyChangedAuto(); + } + } + + /// + /// Gets the current effective x-axis minimum. + /// + public GraphDataPoint EffectiveMinimumX { get; private set; } + + /// + /// Gets the current effective x-axis maximum. + /// + public GraphDataPoint EffectiveMaximumX { get; private set; } + + /// + /// Gets the current effective y-axis minimum. + /// + public GraphDataPoint EffectiveMinimumY { get; private set; } + + /// + /// Gets the current effective y-axis maximum. + /// + public GraphDataPoint EffectiveMaximumY { get; private set; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) x-axis minimum. + /// + public GraphDataPoint VirtualMinimumX { get; private set; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) x-axis maximum. + /// + public GraphDataPoint VirtualMaximumX { get; private set; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) y-axis minimum. + /// + public GraphDataPoint VirtualMinimumY { get; private set; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) y-axis maximum. + /// + public GraphDataPoint VirtualMaximumY { get; private set; } + + #endregion + + #region Commands + + /// + /// Gets the clear command. + /// + public GraphCommand ClearCommand { get; private set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public GraphController() + { + Renderer = new ScrollingLineRenderer(); + + DataSeriesCollection = new ObservableCollection(); + Range = new GraphRange(); + + _last_render_time = DateTime.Now; + _to_render = new Dictionary(); + _pending_series_collection = new GraphDataQueue>(); + RefreshRate = TimeSpan.FromMilliseconds(50); + + ClearCommand = new GraphCommand(Clear); + + _render_thread = new Thread(RenderThreadMethod); + _render_thread.IsBackground = true; + _render_thread.Start(); + } + + #endregion + + #region Render Thread + + /// + /// The rendering thread method. + /// + private void RenderThreadMethod() + { + while (true) + { + if (!IsPaused) + { + var pending_list = _pending_series_collection.BlockDequeue(); + + foreach (var pending_series in pending_list) + { + if (pending_series.IsClearSeries) + { + _pending_series_collection = new GraphDataQueue>(); + _to_render.Clear(); + break; + } + + if (_to_render.ContainsKey(pending_series.Series)) + { + var s = _to_render[pending_series.Series]; + s.XX.AddRange(pending_series.XX); + s.YY.AddRange(pending_series.YY); + } + else + { + _to_render[pending_series.Series] = pending_series; + } + } + + if (DateTime.Now > _last_render_time.AddMilliseconds(RefreshRate.TotalMilliseconds) && _to_render.Count > 0) + { + GraphDataPoint min_x = _range.MaximumX - _range.MaximumX; + GraphDataPoint max_x = _range.MaximumX; + GraphDataPoint min_y = _range.MinimumY; + GraphDataPoint max_y = _range.MaximumY; + + min_x = _to_render.First().Value.XX.First(); + max_x = _to_render.First().Value.XX.Last(); + + if (_range.AutoY) + { + min_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Min(); + max_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Max(); + } + + if (min_y == max_y) + { + min_y = _range.MinimumY; + max_y = _range.MaximumY; + } + + EffectiveMinimumX = min_x; + EffectiveMaximumX = max_x; + EffectiveMinimumY = min_y; + EffectiveMaximumY = max_y; + + VirtualMinimumX = EffectiveMinimumX; + VirtualMaximumX = EffectiveMaximumX; + VirtualMinimumY = EffectiveMinimumY; + VirtualMaximumY = EffectiveMaximumY; + + _last_render_time = DateTime.Now; + + if (Surface != null) + { + var surface_size = Surface.GetSize(); + var zoom_rect = Surface.GetZoomRect(); + + Surface.BeginDraw(); + + if (zoom_rect.Width > 0 && zoom_rect.Height > 0) + { + var zoom_rect_top_percentage = zoom_rect.Top / surface_size.Height; + var zoom_rect_bottom_percentage = zoom_rect.Bottom / surface_size.Height; + var zoom_rect_left_percentage = zoom_rect.Left / surface_size.Width; + var zoom_rect_right_percentage = zoom_rect.Right / surface_size.Width; + + VirtualMinimumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_bottom_percentage); + VirtualMaximumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_top_percentage); + + VirtualMinimumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_left_percentage); + VirtualMaximumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_right_percentage); + + GraphTransform transform = new GraphTransform(); + var scale_x = (float)(surface_size.Width / zoom_rect.Width); + var scale_y = (float)(surface_size.Height / zoom_rect.Height); + var translate_x = (float)-zoom_rect.Left * scale_x; + var translate_y = (float)-zoom_rect.Top * scale_y; + + transform = new GraphTransform(); + transform.TranslateX = translate_x; + transform.TranslateY = translate_y; + transform.ScaleX = scale_x; + transform.ScaleY = scale_y; + + Surface.SetTransform(transform); + } + + List>> to_draw = new List>>(); + + var to_render = _to_render.Select(x => x.Value).ToList(); + + foreach (var item in to_render) + { + var points = Renderer.Render(Surface, item.Series, _range, item.XX, item.YY, min_x, max_x, min_y, max_y); + to_draw.Add(new Tuple>(item.Series, points)); + } + + for (int i = 0; i < to_draw.Count; i++) + { + if (to_draw[i].Item2.Count() > 2) + { + if (to_draw[i].Item1.IsVisible) + { + Renderer.Draw(Surface, to_draw[i].Item1, to_draw[i].Item2, i, to_draw.Count); + } + } + } + + Surface.EndDraw(); + } + + OnEffectiveRangeChanged(EffectiveMinimumX, EffectiveMaximumX, EffectiveMinimumY, EffectiveMaximumY); + OnVirtualRangeChanged(VirtualMinimumX, VirtualMaximumX, VirtualMinimumY, VirtualMaximumY); + } + } + else + { + Thread.Sleep(RefreshRate); + } + } + } + + #endregion + + #region Protected Methods + + /// + /// Raises the event. + /// + /// The minimum x. + /// The maximum x. + /// The minimum y. + /// The maximum y. + protected virtual void OnEffectiveRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY) + { + EffectiveRangeChanged?.Invoke(this, new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY)); + } + + /// + /// Raises the event. + /// + /// The minimum x. + /// The maximum x. + /// The minimum y. + /// The maximum y. + protected virtual void OnVirtualRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY) + { + var range = new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY); + VirtualRangeChanged?.Invoke(this, range); + } + + /// + /// Converts the specified relative x position to graph absolute position. + /// + /// The relative x position. + /// + protected virtual float ConvertXValueToRendererValue(double x) + { + return (float)(x * Surface.GetSize().Width / 100); + } + + /// + /// Converts the specified relative y position to graph absolute position. + /// + /// The relative y position. + /// + protected virtual float ConvertYValueToRendererValue(double y) + { + return (float)(Surface.GetSize().Height - (y * Surface.GetSize().Height / 100)); + } + + #endregion + + #region Public Methods + + /// + /// Submits the specified x and y data points. + /// If the controller has more than one data series the data points will be duplicated. + /// + /// X data point. + /// Y data point. + public void PushData(TXDataPoint x, TYDataPoint y) + { + if (DataSeriesCollection.Count == 0) return; + + List> xxxx = new List>(); + List> yyyy = new List>(); + + foreach (var series in DataSeriesCollection.ToList()) + { + xxxx.Add(new List() { x }); + yyyy.Add(new List() { y }); + } + + PushData(xxxx, yyyy); + } + + /// + /// Submits the specified collections of x and y data points. + /// If the controller has more than one data series the data points will be distributed evenly. + /// + /// X data point collection. + /// Y data point collection. + public void PushData(IEnumerable xx, IEnumerable yy) + { + if (DataSeriesCollection.Count == 0) return; + + var xList = xx.ToList(); + var yList = yy.ToList(); + + List> xxxx = new List>(); + List> yyyy = new List>(); + + foreach (var series in DataSeriesCollection.ToList()) + { + xxxx.Add(new List()); + yyyy.Add(new List()); + } + + int counter = 0; + + for (int i = 0; i < xList.Count; i++) + { + xxxx[counter].Add(xList[i]); + yyyy[counter].Add(yList[i]); + + counter++; + + if (counter >= xxxx.Count) + { + counter = 0; + } + } + + PushData(xxxx, yyyy); + } + + /// + /// Submits a matrix of x and y data points. Meaning each data series should process a single collection of x/y data points. + /// + /// X matrix. + /// Y matrix. + public void PushData(IEnumerable> xxxx, IEnumerable> yyyy) + { + if (DataSeriesCollection.Count == 0) return; + + IEnumerable> xxxxI = xxxx.Select(x => x.ToList()).ToList(); + IEnumerable> yyyyI = yyyy.Select(x => x.ToList()).ToList(); + + List> xxxxList = xxxxI.Select(x => x.ToList()).ToList(); + List> yyyyList = yyyyI.Select(x => x.ToList()).ToList(); + + int first_count_x = xxxxList[0].Count; + int first_count_y = yyyyList[0].Count; + + + bool is_data_valid = true; + + for (int i = 0; i < xxxxList.Count; i++) + { + if (xxxxList[0].Count != first_count_x) + { + is_data_valid = false; + break; + } + + if (xxxxList[0].Count != yyyyList[0].Count) + { + is_data_valid = false; + break; + } + } + + if (!is_data_valid) + { + throw new ArgumentOutOfRangeException("When pushing data to a multi series renderer, each series must contain the same amount of data."); + } + + var list = DataSeriesCollection.ToList(); + + var pending_list = new List(); + + for (int i = 0; i < list.Count; i++) + { + pending_list.Add(new PendingSeries() + { + Series = list[i], + XX = xxxxList[i].ToList(), + YY = yyyyList[i].ToList(), + }); + } + + _pending_series_collection.BlockEnqueue(pending_list); + } + + /// + /// Clears all data points from this controller. + /// + public void Clear() + { + _pending_series_collection.BlockEnqueue(new List() + { + new PendingSeries() + { + IsClearSeries = true + }, + }); + } + + /// + /// Requests the controller to invoke a virtual range change event. + /// + public void RequestVirtualRangeChange() + { + OnVirtualRangeChanged(Range.MaximumX, Range.MaximumX, Range.MinimumY, Range.MaximumY); + } + + #endregion + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPoint.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPoint.cs new file mode 100644 index 000000000..e069d8950 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPoint.cs @@ -0,0 +1,412 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX +{ + /// + /// Represents an base class. + /// + /// + public abstract class GraphDataPoint : IGraphDataPoint + { + #region IComparable + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. + /// + public abstract int CompareTo(object obj); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion + + #region Logical Operators + + /// + /// Implements the operator >. + /// + /// + /// The result of the operator. + /// + public static bool operator >(GraphDataPoint a, GraphDataPoint b) + { + return a.CompareTo(b) == 1; + } + + /// + /// Implements the operator <. + /// + /// + /// The result of the operator. + /// + public static bool operator <(GraphDataPoint a, GraphDataPoint b) + { + return a.CompareTo(b) == -1; + } + + /// + /// Implements the operator ==. + /// + /// + /// The result of the operator. + /// + public static bool operator ==(GraphDataPoint a, GraphDataPoint b) + { + if (Object.ReferenceEquals(a, null) && Object.ReferenceEquals(b, null)) return true; + if (Object.ReferenceEquals(a, null) && !Object.ReferenceEquals(b, null)) return false; + + return a.CompareTo(b) == 0; + } + + /// + /// Implements the operator !=. + /// + /// + /// The result of the operator. + /// + public static bool operator !=(GraphDataPoint a, GraphDataPoint b) + { + if (Object.ReferenceEquals(a, null) && Object.ReferenceEquals(b, null)) return false; + if (Object.ReferenceEquals(a, null) && !Object.ReferenceEquals(b, null)) return true; + + return a.CompareTo(b) != 0; + } + + #endregion + + #region Arithmetic Operators + + /// + /// Implements the operator -. + /// + /// + /// The result of the operator. + /// + public static GraphDataPoint operator -(GraphDataPoint a, GraphDataPoint b) + { + return (GraphDataPoint)a.Subtract(b); + } + + /// + /// Implements the operator +. + /// + /// + /// The result of the operator. + /// + public static GraphDataPoint operator +(GraphDataPoint a, GraphDataPoint b) + { + return (GraphDataPoint)a.Add(b); + } + + /// + /// Implements the operator /. + /// + /// + /// The result of the operator. + /// + public static GraphDataPoint operator /(GraphDataPoint a, GraphDataPoint b) + { + return (GraphDataPoint)a.Divide(b); + } + + /// + /// Implements the operator *. + /// + /// + /// The result of the operator. + /// + public static GraphDataPoint operator *(GraphDataPoint a, GraphDataPoint b) + { + return (GraphDataPoint)a.Multiply(b); + } + + #endregion + + #region IGraphDataPoint + + /// + /// Sums the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public abstract IGraphDataPoint Add(IGraphDataPoint other); + + /// + /// Subtract the value of another instance from this instance and returns the result. + /// + /// The other instance. + /// + public abstract IGraphDataPoint Subtract(IGraphDataPoint other); + + /// + /// Multiplies the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public abstract IGraphDataPoint Multiply(IGraphDataPoint other); + + /// + /// Divides the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + public abstract IGraphDataPoint Divide(IGraphDataPoint other); + + /// + /// Returns the percentage value of this instance between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// + public abstract double ComputeRelativePosition(IGraphDataPoint min, IGraphDataPoint max); + + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + public abstract IGraphDataPoint ComputeAbsolutePosition(IGraphDataPoint min, IGraphDataPoint max, double percentage); + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + public abstract IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count); + + /// + /// Gets the inner value. + /// + /// + public abstract object GetValue(); + + /// + /// Sets the inner value. + /// + /// The value. + public abstract void SetValue(object value); + + /// + /// Returns a formated string of this data point. + /// + /// The format. + /// + public abstract string ToString(string format); + + /// + /// Parses the specified value. + /// + /// The value. + /// + public abstract IGraphDataPoint Parse(string value); + + #endregion + + #region Properties + + /// + /// Gets the type of this graph data point. + /// + public Type Type + { + get { return GetType(); } + } + + #endregion + } + + /// + /// Represents an base class. + /// + /// + /// The type of the data type. + /// + public abstract class GraphDataPoint : GraphDataPoint where T : IComparable where TDataType : GraphDataPoint + { + #region Properties + + /// + /// Gets or sets the encapsulated data point value. + /// + public T Value { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public GraphDataPoint() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public GraphDataPoint(T value) + { + Value = value; + } + + #endregion + + #region IComparable + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. + /// + public override int CompareTo(object obj) + { + if (Object.ReferenceEquals(obj, null)) return -1; + + GraphDataPoint b = obj as GraphDataPoint; + return Value.CompareTo(b.Value); + } + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + var @base = obj as GraphDataPoint; + return @base != null && + EqualityComparer.Default.Equals(Value, @base.Value); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return -1937169414 + EqualityComparer.Default.GetHashCode(Value); + } + + #endregion + + #region ToString + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return Value.ToString(); + } + + #endregion + + #region IGraphDataPoint + + /// + /// Gets the inner value. + /// + /// + public override object GetValue() + { + return Value; + } + + /// + /// Sets the inner value. + /// + /// The value. + public override void SetValue(object value) + { + Value = (T)value; + } + + #endregion + + #region Parsing + + /// + /// Parses the specified value and returns a new instance of data point. + /// + /// The value. + /// + public static TDataType ParseFrom(String value) + { + return Activator.CreateInstance().Parse(value) as TDataType; + } + + #endregion + + #region Implicit Operators + + /// + /// Performs an implicit conversion from to . + /// + /// The value. + /// + /// The result of the conversion. + /// + public static implicit operator GraphDataPoint(T value) + { + return Activator.CreateInstance(typeof(TDataType), value) as GraphDataPoint; + } + + /// + /// Performs an implicit conversion from to . + /// + /// The instance. + /// + /// The result of the conversion. + /// + public static implicit operator T(GraphDataPoint instance) + { + return instance.Value; + } + + #endregion + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointHelper.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointHelper.cs new file mode 100644 index 000000000..b28d7a501 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointHelper.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX +{ + /// + /// Represents an helper class. + /// + public static class GraphDataPointHelper + { + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + public static T ComputeAbsolutePosition(T min, T max, double percentage) where T : class, IGraphDataPoint + { + return (Activator.CreateInstance(min.GetType()) as T).ComputeAbsolutePosition(min, max, percentage) as T; + } + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + public static IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count) where T : class, IGraphDataPoint + { + return (Activator.CreateInstance(min.GetType()) as T).CreateRange(min, max, count) as IEnumerable; + } + + /// + /// Initializes a new instance of the specified type. + /// + /// + /// + public static T Init() where T : class, IGraphDataPoint + { + return Activator.CreateInstance(typeof(T)) as T; + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointTypeConverter.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointTypeConverter.cs new file mode 100644 index 000000000..edbd78cf1 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataPointTypeConverter.cs @@ -0,0 +1,82 @@ +using RealTimeGraphX.DataPoints; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Text; + +namespace RealTimeGraphX +{ + /// + /// Represents the type converter. + /// + public class GraphDataPointTypeConverter : TypeConverter + { + private IGraphDataPoint _instance; + + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert from. + /// + /// true if this converter can perform the conversion; otherwise, false. + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(String); + } + + /// + /// Converts the given object to the type of this converter, using the specified context and culture information. + /// + /// An that provides a format context. + /// The to use as the current culture. + /// The to convert. + /// + /// An that represents the converted value. + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value != null && _instance != null) + { + try + { + return _instance.Parse(value.ToString()); + } + catch {} + } + + return _instance; + } + + /// + /// Returns whether this converter can convert the object to the specified type, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert to. + /// + /// true if this converter can perform the conversion; otherwise, false. + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(String); + } + + /// + /// Converts the given value object to the specified type, using the specified context and culture information. + /// + /// An that provides a format context. + /// A . If null is passed, the current culture is assumed. + /// The to convert. + /// The to convert the value parameter to. + /// + /// An that represents the converted value. + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + _instance = value as IGraphDataPoint; + return value != null ? value.ToString() : null; + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataQueue.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataQueue.cs new file mode 100644 index 000000000..cb28231ab --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphDataQueue.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RealTimeGraphX +{ + /// + /// Represents a blocking concurrent queue for graph data consumption. + /// + /// + /// + public class GraphDataQueue : BlockingCollection + { + /// + /// Initializes a new instance of the GraphDataQueue, Use Add and TryAdd for Enqueue and TryEnqueue and Take and TryTake for Dequeue and TryDequeue functionality + /// + public GraphDataQueue() + : base(new ConcurrentQueue()) + { + } + + /// + /// Initializes a new instance of the GraphDataQueue, Use Add and TryAdd for Enqueue and TryEnqueue and Take and TryTake for Dequeue and TryDequeue functionality + /// + /// + public GraphDataQueue(int maxSize) + : base(new ConcurrentQueue(), maxSize) + { + } + + /// + /// Enqueues the specified item. + /// + /// The item. + public void BlockEnqueue(T item) + { + Add(item); + } + + /// + /// Blocks until an item is available for dequeuing. + /// + /// + public T BlockDequeue() + { + return Take(); + } + + /// + /// Blocks until an item is available for dequeuing. + /// + /// The cancellation token. + /// + public T BlockDequeue(CancellationToken cancellationToken) + { + return Take(cancellationToken); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphObject.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphObject.cs new file mode 100644 index 000000000..d37110c5c --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphObject.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace RealTimeGraphX +{ + /// + /// Represents an supported graph object. + /// + /// + public abstract class GraphObject : INotifyPropertyChanged + { + /// + /// Occurs when a property value changes. + /// + [field: NonSerialized] + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Raises the property changed event. + /// + /// Name of the property. + protected virtual void RaisePropertyChangedAuto([CallerMemberName] string caller = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller)); + } + + /// + /// Raises the property changed event. + /// + /// Name of the property. + protected virtual void RaisePropertyChanged(String propName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRange.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRange.cs new file mode 100644 index 000000000..521f9f0b6 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRange.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph x/y data points boundaries. + /// + public interface IGraphRange : IGraphComponent + { + /// + /// Gets or sets the maximum x value. + /// + GraphDataPoint MaximumX { get; set; } + + /// + /// Gets or sets the minimum x value. + /// + GraphDataPoint MinimumY { get; set; } + + /// + /// Gets or sets the maximum y value. + /// + GraphDataPoint MaximumY { get; set; } + + /// + /// Gets or sets a value indicating whether to automatically adjust the graph and according to the current effective data points. + /// + bool AutoY { get; set; } + } + + /// + /// Represents a graph X/Y range boundaries. + /// + /// Type of x-axis data point. + /// Type of y-axis data point. + /// + public class GraphRange : GraphObject, IGraphRange where XDataPoint : GraphDataPoint where YDataPoint : GraphDataPoint + { + private XDataPoint _maximumX; + /// + /// Gets or sets the maximum x value. + /// + public XDataPoint MaximumX + { + get { return _maximumX; } + set { _maximumX = value; RaisePropertyChangedAuto(); } + } + + private YDataPoint _minimumY; + /// + /// Gets or sets the minimum x value. + /// + public YDataPoint MinimumY + { + get { return _minimumY; } + set { _minimumY = value; RaisePropertyChangedAuto(); } + } + + private YDataPoint _maximumY; + /// + /// Gets or sets the maximum y value. + /// + public YDataPoint MaximumY + { + get { return _maximumY; } + set { _maximumY = value; RaisePropertyChangedAuto(); } + } + + private bool _autoY; + /// + /// Gets or sets a value indicating whether to automatically adjust the graph and according to the current visible data points. + /// + public bool AutoY + { + get { return _autoY; } + set { _autoY = value; RaisePropertyChangedAuto(); } + } + + /// + /// Initializes a new instance of the class. + /// + public GraphRange() + { + MinimumY = GraphDataPointHelper.Init(); + MaximumX = GraphDataPointHelper.Init(); + MaximumY = GraphDataPointHelper.Init(); + } + + /// + /// Gets or sets the maximum x value. + /// + GraphDataPoint IGraphRange.MaximumX + { + get + { + return MaximumX; + } + set + { + MaximumX = (XDataPoint)value; + } + } + + /// + /// Gets or sets the minimum x value. + /// + GraphDataPoint IGraphRange.MinimumY + { + get + { + return MinimumY; + } + set + { + MinimumY = (YDataPoint)value; + } + } + + /// + /// Gets or sets the maximum y value. + /// + GraphDataPoint IGraphRange.MaximumY + { + get + { + return MaximumY; + } + set + { + MaximumY = (YDataPoint)value; + } + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRenderer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRenderer.cs new file mode 100644 index 000000000..c42fcb4ab --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphRenderer.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using System.Drawing; + +namespace RealTimeGraphX +{ + /// + /// Represents an base class. + /// + /// The type of the data series. + /// + /// + public abstract class GraphRenderer : GraphObject, IGraphRenderer where TDataSeries : IGraphDataSeries + { + /// + /// Converts the specified relative x position to a graph surface absolute position. + /// + /// The target surface. + /// The relative x position. + /// + protected virtual float ConvertXValueToRendererValue(IGraphSurface surface, double x) + { + return (float)(x * surface.GetSize().Width / 100); + } + + /// + /// Converts the specified relative y position to a graph surface absolute position. + /// + /// The surface. + /// The relative y position. + /// + protected virtual float ConvertYValueToRendererValue(IGraphSurface surface, double y) + { + return (float)(surface.GetSize().Height - (y * surface.GetSize().Height / 100)); + } + + /// + /// Arranges the series of data points and returns a series of drawing points. + /// + /// The target graph surface. + /// The instance of the current rendered data series. + /// Instance of graph range. + /// Collection of x coordinates. + /// Collection of y coordinates. + /// The minimum x coordinates value. + /// The maximum x coordinates value. + /// The minimum y coordinates value. + /// The maximum y coordinates value. + /// + public abstract IEnumerable Render(IGraphSurface surface, TDataSeries series, IGraphRange range, List xx, List yy, GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY); + + /// + /// Draws the specified data series points on the target surface. + /// + /// The target graph surface. + /// The instance of the current rendered data series. + /// The collection of the current data series drawing points. + /// The index of the current data series within the collection of data series. + /// The length of the data series collection. + public abstract void Draw(IGraphSurface surface, TDataSeries series, IEnumerable points, int index, int count); + + /// + /// Gets a closed polygon version of the specified drawing points. + /// + /// The target surface. + /// The drawing points. + /// + protected virtual IEnumerable GetFillPoints(IGraphSurface surface, IEnumerable points) + { + List closed = points.ToList(); + closed.Add(new PointF(points.Last().X, surface.GetSize().Width)); + closed.Add(new PointF(0, surface.GetSize().Height)); + return closed.ToArray(); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphTransform.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphTransform.cs new file mode 100644 index 000000000..efcf4b052 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/GraphTransform.cs @@ -0,0 +1,39 @@ +namespace RealTimeGraphX +{ + /// + /// Represents a graph transformation. + /// + public class GraphTransform + { + /// + /// Gets or sets the horizontal scale factor. + /// + public float ScaleX { get; set; } + + /// + /// Gets or sets the vertical scale factor. + /// + public float ScaleY { get; set; } + + /// + /// Gets or sets the horizontal translate transformation. + /// + public float TranslateX { get; set; } + + /// + /// Gets or sets the vertical translate transformation. + /// + public float TranslateY { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public GraphTransform() + { + ScaleX = 1; + ScaleY = 1; + TranslateX = 0; + TranslateY = 0; + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphComponent.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphComponent.cs new file mode 100644 index 000000000..dd8f4ee1d --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphComponent.cs @@ -0,0 +1,10 @@ +namespace RealTimeGraphX +{ + /// + /// Represents a RealTimeGraphX component. + /// + public interface IGraphComponent + { + + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphController.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphController.cs new file mode 100644 index 000000000..0dbc49ccb --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphController.cs @@ -0,0 +1,185 @@ +using RealTimeGraphX.EventArguments; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph controller. + /// + /// + public interface IGraphController : IGraphComponent + { + #region Events + + /// + /// Occurs when the current effective minimum/maximum values have changed. + /// + event EventHandler EffectiveRangeChanged; + + /// + /// Occurs when the current virtual (effective minimum/maximum after transformation) minimum/maximum values have changed. + /// + event EventHandler VirtualRangeChanged; + + #endregion + + #region Properties + + /// + /// Gets or sets the controller refresh rate. + /// Higher rate requires more CPU time. + /// + TimeSpan RefreshRate { get; set; } + + /// + /// Gets or sets a value indicating whether to pause the rendering. + /// + bool IsPaused { get; set; } + + /// + /// Gets the current effective x-axis minimum. + /// + GraphDataPoint EffectiveMinimumX { get; } + + /// + /// Gets the current effective x-axis maximum. + /// + GraphDataPoint EffectiveMaximumX { get; } + + /// + /// Gets the current effective y-axis minimum. + /// + GraphDataPoint EffectiveMinimumY { get; } + + /// + /// Gets the current effective y-axis maximum. + /// + GraphDataPoint EffectiveMaximumY { get; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) x-axis minimum. + /// + GraphDataPoint VirtualMinimumX { get; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) x-axis maximum. + /// + GraphDataPoint VirtualMaximumX { get; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) y-axis minimum. + /// + GraphDataPoint VirtualMinimumY { get; } + + /// + /// Gets the current virtual (effective minimum/maximum after transformation) y-axis maximum. + /// + GraphDataPoint VirtualMaximumY { get; } + + /// + /// Clears all data points from this controller. + /// + void Clear(); + + #endregion + + #region Commands + + /// + /// Gets the clear command. + /// + GraphCommand ClearCommand { get; } + + #endregion + + #region Methods + + /// + /// Requests the controller to invoke a virtual range change event. + /// + void RequestVirtualRangeChange(); + + #endregion + } + + /// + /// Represents a graph controller capable of pushing data points to it's associated Graph Renderer + /// and rendering them to it's associated Graph Surface. + /// + /// The type of the data series. + /// + public interface IGraphController : IGraphController where TDataSeries : IGraphDataSeries + { + #region Properties + + /// + /// Gets the data series collection. + /// + ObservableCollection DataSeriesCollection { get; } + + /// + /// Gets or sets the graph renderer. + /// + IGraphRenderer Renderer { get; set; } + + /// + /// Gets or sets the graph surface. + /// + IGraphSurface Surface { get; set; } + + #endregion + } + + + /// + /// Represents a graph controller capable of pushing data points to it's associated Graph Renderer + /// and rendering them to it's associated Graph Surface. + /// + /// The type of the data series. + /// The type of the x data point. + /// The type of the y data point. + /// + public interface IGraphController : IGraphController + where TXDataPoint : GraphDataPoint + where TYDataPoint : GraphDataPoint + where TDataSeries : IGraphDataSeries + { + #region Properties + + /// + /// Gets or sets the graph range (data point boundaries). + /// + GraphRange Range { get; set; } + + #endregion + + #region Methods + + /// + /// Submits the specified x and y data points. + /// If the controller has more than one data series the data points will be duplicated. + /// + /// X data point. + /// Y data point. + void PushData(TXDataPoint x, TYDataPoint y); + + /// + /// Submits the specified collections of x and y data points. + /// If the controller has more than one data series the data points will be distributed evenly. + /// + /// X data point collection. + /// Y data point collection. + void PushData(IEnumerable xx, IEnumerable yy); + + /// + /// Submits a matrix of x and y data points. Meaning each data series should process a single collection of x/y data points. + /// + /// X matrix. + /// Y matrix. + void PushData(IEnumerable> xxxx, IEnumerable> yyyy); + + #endregion + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataPoint.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataPoint.cs new file mode 100644 index 000000000..9ecae4a8a --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataPoint.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph X or Y data point that can be pushed to a Graph Controller. + /// + /// + [TypeConverter(typeof(GraphDataPointTypeConverter))] + public interface IGraphDataPoint : IComparable + { + /// + /// Sums the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + IGraphDataPoint Add(IGraphDataPoint other); + + /// + /// Subtract the value of another instance from this instance and returns the result. + /// + /// The other instance. + /// + IGraphDataPoint Subtract(IGraphDataPoint other); + + /// + /// Multiplies the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + IGraphDataPoint Multiply(IGraphDataPoint other); + + /// + /// Divides the value of this instance with another instance value and returns the result. + /// + /// The other instance. + /// + IGraphDataPoint Divide(IGraphDataPoint other); + + /// + /// Creates a range of values from the specified minimum and maximum. + /// + /// The minimum. + /// The maximum. + /// The count. + /// + IEnumerable CreateRange(IGraphDataPoint min, IGraphDataPoint max, int count); + + /// + /// Returns the percentage value of this instance between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// + double ComputeRelativePosition(IGraphDataPoint min, IGraphDataPoint max); + + /// + /// Returns the absolute value of the specified percentage value between the specified minimum and maximum values. + /// + /// The minimum. + /// The maximum. + /// The percentage. + /// + IGraphDataPoint ComputeAbsolutePosition(IGraphDataPoint min, IGraphDataPoint max, double percentage); + + /// + /// Gets the encapsulated value. + /// + /// + object GetValue(); + + /// + /// Sets the encapsulated value. + /// + /// The value. + void SetValue(object value); + + /// + /// Returns a formated string of this data point. + /// + /// The format. + /// + String ToString(String format); + + /// + /// Parses the specified value. + /// + /// The value. + /// + IGraphDataPoint Parse(String value); + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataSeries.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataSeries.cs new file mode 100644 index 000000000..dd05da92b --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphDataSeries.cs @@ -0,0 +1,31 @@ +using System; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph data series. + /// + /// + public interface IGraphDataSeries : IGraphComponent + { + /// + /// Gets or sets the series name. + /// + String Name { get; set; } + + /// + /// Gets or sets the stroke thickness. + /// + float StrokeThickness { get; set; } + + /// + /// Gets or sets a value indicating whether to fill the series. + /// + bool UseFill { get; } + + /// + /// Gets or sets a value indicating whether this series should be visible. + /// + bool IsVisible { get; set; } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphRenderer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphRenderer.cs new file mode 100644 index 000000000..fdd3b17e8 --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphRenderer.cs @@ -0,0 +1,41 @@ +using RealTimeGraphX.EventArguments; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph renderer capable of receiving a series of data points from a controller and transforming them to drawing points. + /// + /// The type of the data series. + /// + public interface IGraphRenderer : IGraphComponent where TDataSeries : IGraphDataSeries + { + /// + /// Arranges the series of data points and returns a series of drawing points. + /// + /// The target graph surface. + /// The instance of the current rendered data series. + /// Instance of graph range. + /// Collection of x coordinates. + /// Collection of y coordinates. + /// The minimum x coordinates value. + /// The maximum x coordinates value. + /// The minimum y coordinates value. + /// The maximum y coordinates value. + /// + IEnumerable Render(IGraphSurface surface, TDataSeries series, IGraphRange range, List xx, List yy, GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY); + + /// + /// Draws the specified data series points on the target surface. + /// + /// The target graph surface. + /// The instance of the current rendered data series. + /// The collection of the current data series drawing points. + /// The index of the current data series within the collection of data series. + /// The length of the data series collection. + void Draw(IGraphSurface surface, TDataSeries series, IEnumerable points, int index, int count); + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphSurface.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphSurface.cs new file mode 100644 index 000000000..310bc7c3d --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/IGraphSurface.cs @@ -0,0 +1,64 @@ +using RealTimeGraphX.EventArguments; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace RealTimeGraphX +{ + /// + /// Represents a graph drawing surface capable of drawing a series of points submitted by a graph renderer. + /// + public interface IGraphSurface : IGraphComponent + { + /// + /// Returns the actual size of the surface. + /// + /// + SizeF GetSize(); + + /// + /// Returns the current bounds of the zooming rectangle. + /// + /// + RectangleF GetZoomRect(); + } + + /// + /// Represents a graph drawing surface capable of drawing a series of points submitted by a graph renderer. + /// + /// The type of the data series. + /// + public interface IGraphSurface : IGraphSurface where TDataSeries : IGraphDataSeries + { + /// + /// Called before drawing has started. + /// + void BeginDraw(); + + /// + /// Draws the stroke of the specified data series points. + /// + /// The data series. + /// The points. + void DrawSeries(TDataSeries dataSeries, IEnumerable points); + + /// + /// Fills the specified data series points. + /// + /// The data series. + /// The points. + void FillSeries(TDataSeries dataSeries, IEnumerable points); + + /// + /// Applies transformation on the current pass. + /// + /// The transform. + void SetTransform(GraphTransform transform); + + /// + /// Called when drawing has completed. + /// + void EndDraw(); + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/RealTimeGraphX.csproj b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/RealTimeGraphX.csproj new file mode 100644 index 000000000..9f5c4f4ab --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/RealTimeGraphX.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/Renderers/ScrollingLineRenderer.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/Renderers/ScrollingLineRenderer.cs new file mode 100644 index 000000000..82e80aa0f --- /dev/null +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX-master/RealTimeGraphX/Renderers/ScrollingLineRenderer.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +namespace RealTimeGraphX.Renderers +{ + /// + /// Represents a scrolling style . + /// + /// The type of the data series. + /// + public class ScrollingLineRenderer : GraphRenderer where TDataSeries : IGraphDataSeries + { + /// + /// Arranges the series of data points and returns a series of drawing points. + /// + /// The target graph surface. + /// The instance of the current rendered data series. + /// Instance of graph range. + /// Collection of x coordinates. + /// Collection of y coordinates. + /// The minimum x coordinates value. + /// The maximum x coordinates value. + /// The minimum y coordinates value. + /// The maximum y coordinates value. + /// + public override IEnumerable Render(IGraphSurface surface, TDataSeries series, IGraphRange range, List xx, List yy, GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY) + { + var dxList = xx.Select(x => x.ComputeRelativePosition(minimumX, maximumX)).ToList(); + var dyList = yy.Select(x => x.ComputeRelativePosition(minimumY, maximumY)).ToList(); + + if (maximumX - minimumX > range.MaximumX) + { + var offset = ((maximumX - minimumX) - range.MaximumX) + minimumX; + + for (int i = 0; i < xx.Count; i++) + { + if (xx[i] < offset) + { + xx.RemoveAt(i); + yy.RemoveAt(i); + i--; + } + else + { + break; + } + } + } + + List points = new List(); + + for (int i = 0; i < dxList.Count; i++) + { + float image_x = ConvertXValueToRendererValue(surface,dxList[i]); + float image_y = (float)Math.Min(ConvertYValueToRendererValue(surface, dyList[i]), surface.GetSize().Height - 2); + + PointF point = new PointF(image_x, image_y); + points.Add(point); + } + + return points; + } + + /// + /// Draws the specified data series points on the target surface. + /// + /// The target graph surface. + /// The instance of the current rendered data series. + /// The collection of the current data series drawing points. + /// The index of the current data series within the collection of data series. + /// The length of the data series collection. + public override void Draw(IGraphSurface surface, TDataSeries series, IEnumerable points, int index, int count) + { + if (series.UseFill) + { + surface.FillSeries(series, GetFillPoints(surface, points)); + } + + surface.DrawSeries(series, points); + } + } +} diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj b/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj index da7d14ce6..fa5852efa 100644 --- a/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj @@ -91,12 +91,6 @@ Settings.Designer.cs - - - {518eb7c5-9e1d-4c23-a9c1-02ee73c25406} - RealTimeGraphX - - diff --git a/Software/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln index 265ab727c..c83bdd628 100644 --- a/Software/Visual_Studio/Tango.sln +++ b/Software/Visual_Studio/Tango.sln @@ -104,8 +104,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.DragAndDrop", "Tango. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.MachineStudio.Technician", "MachineStudio\Modules\Tango.MachineStudio.Technician\Tango.MachineStudio.Technician.csproj", "{5D39C1E1-3ECD-4634-BD1B-2BCF71C54A15}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTimeGraphEx", "SideChains\RealTimeGraphEx\RealTimeGraphEx.csproj", "{B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.SQLiteGenerator.CLI", "Utilities\Tango.SQLiteGenerator.CLI\Tango.SQLiteGenerator.CLI.csproj", "{8A03ADC0-991B-4DA8-8A19-E1D03F92E81C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorMine", "SideChains\ColorMine\ColorMine.csproj", "{37E4CEAB-B54B-451F-B535-04CF7DA9C459}" @@ -212,10 +210,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.WiFi", "Tango.WiFi\Ta EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.MachineStudio.RML", "MachineStudio\Modules\Tango.MachineStudio.RML\Tango.MachineStudio.RML.csproj", "{D0186AC0-0FCF-4D3B-9619-54812B6E524B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RealTimeGraphX", "SideChains\RealTimeGraphX\RealTimeGraphX.csproj", "{6D55A3B8-46D3-493A-A143-AEBD2B98D683}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTimeGraphX.WPF", "SideChains\RealTimeGraphX.WPF\RealTimeGraphX.WPF.csproj", "{99D233C5-FEE7-418E-9C25-D4584CB52E28}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xceed.Wpf.Toolkit", "SideChains\WpfExtendedToolKit\ExtendedWPFToolkitSolution\Src\Xceed.Wpf.Toolkit\Xceed.Wpf.Toolkit.csproj", "{72E591D6-8F83-4D8C-8F67-9C325E623234}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.MachineStudio.Statistics", "MachineStudio\Modules\Tango.MachineStudio.Statistics\Tango.MachineStudio.Statistics.csproj", "{8A65AD6A-A9B4-48C0-9301-4B7434B712F8}" @@ -297,6 +291,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Scripting.IDE.UI", "S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.CatalogImporter", "Utilities\Tango.CatalogImporter\Tango.CatalogImporter.csproj", "{1066BC62-F167-4FC3-8F8B-982A9F632B4A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RealTimeGraphX", "SideChains\RealTimeGraphX-master\RealTimeGraphX\RealTimeGraphX.csproj", "{F13A489C-80EE-4CD0-BDD4-92D959215646}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTimeGraphX.WPF", "SideChains\RealTimeGraphX-master\RealTimeGraphX.WPF\RealTimeGraphX.WPF.csproj", "{6B9774F7-960D-438E-AD81-C6B9BE328D50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTimeGraphX.WPF.Demo", "SideChains\RealTimeGraphX-master\RealTimeGraphX.WPF.Demo\RealTimeGraphX.WPF.Demo.csproj", "{B822CBD9-1113-4668-85C9-22AA9C24CE60}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AppVeyor|Any CPU = AppVeyor|Any CPU @@ -1876,45 +1876,6 @@ Global {5D39C1E1-3ECD-4634-BD1B-2BCF71C54A15}.Release|x64.Build.0 = Release|Any CPU {5D39C1E1-3ECD-4634-BD1B-2BCF71C54A15}.Release|x86.ActiveCfg = Release|Any CPU {5D39C1E1-3ECD-4634-BD1B-2BCF71C54A15}.Release|x86.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|Any CPU.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|ARM.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|ARM.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|ARM64.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|x64.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|x64.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|x86.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.AppVeyor|x86.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|ARM.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|ARM.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|ARM64.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|x64.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|x64.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|x86.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Debug|x86.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|ARM.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|x64.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.DefaultBuild|x86.Build.0 = Debug|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|Any CPU.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|ARM.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|ARM.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|ARM64.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|ARM64.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|x64.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|x64.Build.0 = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|x86.ActiveCfg = Release|Any CPU - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC}.Release|x86.Build.0 = Release|Any CPU {8A03ADC0-991B-4DA8-8A19-E1D03F92E81C}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU {8A03ADC0-991B-4DA8-8A19-E1D03F92E81C}.AppVeyor|Any CPU.Build.0 = Release|Any CPU {8A03ADC0-991B-4DA8-8A19-E1D03F92E81C}.AppVeyor|ARM.ActiveCfg = Release|Any CPU @@ -3801,86 +3762,6 @@ Global {D0186AC0-0FCF-4D3B-9619-54812B6E524B}.Release|x64.Build.0 = Release|Any CPU {D0186AC0-0FCF-4D3B-9619-54812B6E524B}.Release|x86.ActiveCfg = Release|Any CPU {D0186AC0-0FCF-4D3B-9619-54812B6E524B}.Release|x86.Build.0 = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|Any CPU.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|Any CPU.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|ARM.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|ARM.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|ARM64.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|ARM64.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|x64.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|x64.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|x86.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.AppVeyor|x86.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|ARM.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|ARM.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|ARM64.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|x64.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|x64.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|x86.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Debug|x86.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|ARM.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|x64.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.DefaultBuild|x86.Build.0 = Debug|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|Any CPU.Build.0 = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|ARM.ActiveCfg = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|ARM.Build.0 = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|ARM64.ActiveCfg = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|ARM64.Build.0 = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|x64.ActiveCfg = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|x64.Build.0 = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|x86.ActiveCfg = Release|Any CPU - {6D55A3B8-46D3-493A-A143-AEBD2B98D683}.Release|x86.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|Any CPU.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|ARM.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|ARM.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|ARM64.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|x64.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|x64.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|x86.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.AppVeyor|x86.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|ARM.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|ARM.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|ARM64.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|x64.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|x64.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|x86.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Debug|x86.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|ARM.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|x64.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.DefaultBuild|x86.Build.0 = Debug|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|Any CPU.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|ARM.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|ARM.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|ARM64.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|ARM64.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|x64.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|x64.Build.0 = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|x86.ActiveCfg = Release|Any CPU - {99D233C5-FEE7-418E-9C25-D4584CB52E28}.Release|x86.Build.0 = Release|Any CPU {72E591D6-8F83-4D8C-8F67-9C325E623234}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU {72E591D6-8F83-4D8C-8F67-9C325E623234}.AppVeyor|Any CPU.Build.0 = Release|Any CPU {72E591D6-8F83-4D8C-8F67-9C325E623234}.AppVeyor|ARM.ActiveCfg = Release|Any CPU @@ -5284,6 +5165,126 @@ Global {1066BC62-F167-4FC3-8F8B-982A9F632B4A}.Release|x64.Build.0 = Release|Any CPU {1066BC62-F167-4FC3-8F8B-982A9F632B4A}.Release|x86.ActiveCfg = Release|Any CPU {1066BC62-F167-4FC3-8F8B-982A9F632B4A}.Release|x86.Build.0 = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|Any CPU.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|Any CPU.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|ARM.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|ARM.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|ARM64.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|ARM64.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|x64.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|x64.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|x86.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.AppVeyor|x86.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|ARM.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|ARM.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|ARM64.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|x64.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|x64.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|x86.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Debug|x86.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|Any CPU.Build.0 = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|ARM.ActiveCfg = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|ARM.Build.0 = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|ARM64.ActiveCfg = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|ARM64.Build.0 = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|x64.ActiveCfg = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|x64.Build.0 = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|x86.ActiveCfg = Release|Any CPU + {F13A489C-80EE-4CD0-BDD4-92D959215646}.Release|x86.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|ARM.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|x64.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.AppVeyor|x86.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|ARM.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|ARM.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|ARM64.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|x64.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|x64.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|x86.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Debug|x86.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|Any CPU.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|ARM.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|ARM.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|ARM64.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|ARM64.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|x64.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|x64.Build.0 = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|x86.ActiveCfg = Release|Any CPU + {6B9774F7-960D-438E-AD81-C6B9BE328D50}.Release|x86.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|ARM.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|x64.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.AppVeyor|x86.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|ARM.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|ARM64.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|x64.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|x64.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|x86.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Debug|x86.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|Any CPU.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|ARM.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|ARM.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|ARM64.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|ARM64.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|x64.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|x64.Build.0 = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|x86.ActiveCfg = Release|Any CPU + {B822CBD9-1113-4668-85C9-22AA9C24CE60}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5310,7 +5311,6 @@ Global {22C2AA72-9493-4D0D-B421-8EF9789FB192} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8} {D0CE8122-077D-42A2-9490-028AE4769B52} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8} {5D39C1E1-3ECD-4634-BD1B-2BCF71C54A15} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8} - {B9AE25D6-BE35-492F-9079-21A7F3E6F7CC} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} {8A03ADC0-991B-4DA8-8A19-E1D03F92E81C} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760} {37E4CEAB-B54B-451F-B535-04CF7DA9C459} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} {5B954D98-4020-4AC6-939F-C52B5646E8E6} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760} @@ -5345,8 +5345,6 @@ Global {D0E71A4D-9EEA-4F07-983F-EEB4416C587F} = {C81ED1A3-D18C-4D80-A8F5-061994A14A60} {0BDA9B52-9879-4C5E-84E3-81D00B75DACC} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760} {D0186AC0-0FCF-4D3B-9619-54812B6E524B} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8} - {6D55A3B8-46D3-493A-A143-AEBD2B98D683} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} - {99D233C5-FEE7-418E-9C25-D4584CB52E28} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} {72E591D6-8F83-4D8C-8F67-9C325E623234} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} {8A65AD6A-A9B4-48C0-9301-4B7434B712F8} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8} {4EDCF067-E377-42CB-A18C-8368CF484577} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760} @@ -5379,14 +5377,17 @@ Global {C9F60285-91FB-4293-BCF5-164D76755CDD} = {3D750293-C243-48F6-9112-A6B3FF650C0D} {B0EFE7A0-7039-4DC4-8B39-465E521299F6} = {3D750293-C243-48F6-9112-A6B3FF650C0D} {1066BC62-F167-4FC3-8F8B-982A9F632B4A} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760} + {F13A489C-80EE-4CD0-BDD4-92D959215646} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} + {6B9774F7-960D-438E-AD81-C6B9BE328D50} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} + {B822CBD9-1113-4668-85C9-22AA9C24CE60} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - BuildVersion_UseGlobalSettings = False - BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs - BuildVersion_StartDate = 2000/1/1 - BuildVersion_UpdateFileVersion = False - BuildVersion_UpdateAssemblyVersion = True - BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6} + BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear + BuildVersion_UpdateAssemblyVersion = True + BuildVersion_UpdateFileVersion = False + BuildVersion_StartDate = 2000/1/1 + BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs + BuildVersion_UseGlobalSettings = False EndGlobalSection EndGlobal -- cgit v1.3.1