diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-08-12 02:12:46 +0300 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-08-12 02:12:46 +0300 |
| commit | bd72c7efe687dfaca6d4fd3c0fc2b5a39d57df55 (patch) | |
| tree | 0d238b22049d445b40a25ca95f5fef2a1c725bbd /Software/Visual_Studio | |
| parent | 3cfc4ee06b801e581107f24e691451cd291b6a70 (diff) | |
| download | Tango-bd72c7efe687dfaca6d4fd3c0fc2b5a39d57df55.tar.gz Tango-bd72c7efe687dfaca6d4fd3c0fc2b5a39d57df55.zip | |
More work on proc_doc.
Fixed app crash on code editor selection.
Diffstat (limited to 'Software/Visual_Studio')
28 files changed, 2140 insertions, 1214 deletions
diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Content/Welcome.aml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Content/Welcome.aml index 29e0c47c7..eb51fa37f 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Content/Welcome.aml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Content/Welcome.aml @@ -1,59 +1,111 @@ <?xml version="1.0" encoding="utf-8"?> <topic id="0f61f11a-7ea4-4d45-b34a-1f31a2c77a5a" revisionNumber="1"> <developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink"> - <introduction> - <para> -This is a sample conceptual topic. You can use this as a starting point for adding more conceptual -content to your help project. -<codeEntityReference>T:Tango.FSE.Procedures.IProcedureContext</codeEntityReference> -Success! - </para> - </introduction> + + <section> + <title>Welcome</title> + <content> + <para>Welcome to the Tango FSE Procedures API documentation.</para> + <para>This document contains a detailed description of each part of the API with some examples.</para> - <section> - <title>Getting Started</title> - <content> - <para>To get started, add a documentation source to the project (a Visual Studio solution, project, or -assembly and XML comments file). See the <legacyBold>Getting Started</legacyBold> topics in the Sandcastle Help -File Builder's help file for more information. The following default items are included in this project:</para> + <para> + The best way to start exploring the main capabilities of a procedure is the <codeEntityReference>T:Tango.FSE.Procedures.IProcedureContext</codeEntityReference> interface. + </para> + </content> + </section> - <list class="bullet"> - <listItem> - <para><localUri>ContentLayout.content</localUri> - Use the content layout file to manage the -conceptual content in the project and define its layout in the table of contents.</para> - </listItem> + <section> + <title>What is a Procedure ?</title> + <content> + <list class="bullet"> + <listItem> + <para> + A procedure is a sequence of a pre-programmed actions/commands. + </para> + </listItem> - <listItem> - <para>The <localUri>.\media</localUri> folder - Place images in this folder that you will reference -from conceptual content using <codeInline>medialLink</codeInline> or <codeInline>mediaLinkInline</codeInline> -elements. If you will not have any images in the file, you may remove this folder.</para> - </listItem> + <listItem> + <para> + A procedure can have one or more user input parameters. + </para> + </listItem> - <listItem> - <para>The <localUri>.\icons</localUri> folder - This contains a default logo for the help file. You -may replace it or remove it and the folder if not wanted. If removed or if you change the file name, update -the <ui>Transform Args</ui> project properties page by removing or changing the filename in the -<codeInline>logoFile</codeInline> transform argument. Note that unlike images referenced from conceptual topics, -the logo file should have its <legacyBold>BuildAction</legacyBold> property set to <codeInline>Content</codeInline>.</para> - </listItem> + <listItem> + <para> + A procedure can have one or more output values. + </para> + </listItem> + </list> - <listItem> - <para>The <localUri>.\Content</localUri> folder - Use this to store your conceptual topics. You may -name the files and organize them however you like. One suggestion is to lay the files out on disk as you have -them in the content layout file as shown in this project but the choice is yours. Files can be added via the -Solution Explorer or from within the content layout file editor. Files must appear in the content layout file -in order to be compiled into the help file.</para> - </listItem> - </list> + <para> + Procedures are not legacy “stubs”. They are a completely different creature. + <lineBreak/> + Procedures are a way for providing immediate and dynamic response for almost any scenario or issue that is being raised by customers/technicians. + <lineBreak/> + Procedure programmers can leverage an intuitive and extensive API and accomplish almost any requirement. + <lineBreak/> + Programming and managing Procedures is done through the “Procedure Designer” module. + </para> + </content> + </section> - <para>See the <legacyBold>Conceptual Content</legacyBold> topics in the Sandcastle Help File Builder's -help file for more information. See the <legacyBold> Sandcastle MAML Guide</legacyBold> for details on Microsoft -Assistance Markup Language (MAML) which is used to create these topics.</para> - </content> - </section> + <section> + <title>The Procedure Designer</title> + <content> + <para> + The procedure designer module can be seen as a fully fledged IDE (Integrated Development Environment), designed to provide the best development experience for the “procedure programmer”. + </para> + <para> + The procedure designer is basically a project editor. In the technical sense, a “Procedure” is basically a project. + </para> + <para> + A project can be composed of one or more script files that are automatically linked together. + </para> + <para> + A project, is represented as a single file (.pproj) and is designed to encapsulate all the files and information about the project. + </para> + <para> + A project can have multiple assembly references and make use of them. An assembly reference can be any DLL file from the entire .NET framework, or from Twine’s libraries. + </para> + <para> + A “Procedure” in the “Procedures Module” is actually a single published procedure project. + <lineBreak/> + A published procedure is a project that is saved on Twine’s global database (per environment). + <lineBreak/> + Each published project has a name, description and version, while preserving the history of the last 10 versions. + <lineBreak/> + When the user navigates to the Procedures Module, Tango FSE will retrieves all published projects from the server. + </para> + <para> + Accessing the procedure designer module requires the “Run Procedure Designer” permission. + <lineBreak/> + Publishing a procedure project requires the “Publish Procedure Projects” permission. + </para> - <relatedTopics> - <codeEntityReference>T:Tango.FSE.Procedures.IProcedureContext</codeEntityReference> - </relatedTopics> + <legacyBold> + <legacyUnderline> + Summery: + </legacyUnderline> + </legacyBold> + + <list class="bullet"> + <listItem> + <para> + Published procedure projects are intended for all users and can be executed using the Procedures Module. + </para> + </listItem> + + <listItem> + <para> + Procedure Project files are intended for Twine’s programmers and technicians and can be executed/edited using the Procedure Designer. + </para> + </listItem> + </list> + + <para> + We can say that once a procedure project that is being designed for end-users, is well-tested and optimized, it can be published and be available as a “Procedure” for end-users. + </para> + </content> + </section> </developerConceptualDocument> </topic> diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Help/proc-doc.chm b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Help/proc-doc.chm Binary files differindex 24e7db2e2..3ac22ce67 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Help/proc-doc.chm +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Help/proc-doc.chm diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Tango.FSE.Procedures.Documentation.shfbproj b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Tango.FSE.Procedures.Documentation.shfbproj index 78ae89b52..16251df8f 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Tango.FSE.Procedures.Documentation.shfbproj +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures.Documentation/Tango.FSE.Procedures.Documentation.shfbproj @@ -33,6 +33,7 @@ <DocumentationSources> <DocumentationSource sourceFile="..\Tango.FSE.Procedures\Tango.FSE.Procedures.csproj" /> <DocumentationSource sourceFile="..\..\Tango.FSE.Common\Tango.FSE.Common.csproj" /> + <DocumentationSource sourceFile="..\..\..\PPC\Tango.PPC.Shared\Tango.PPC.Shared.csproj" /> </DocumentationSources> <HelpFileFormat>HtmlHelp1</HelpFileFormat> <SyntaxFilters>C#</SyntaxFilters> @@ -102,7 +103,6 @@ <Filter entryType="Namespace" fullName="Tango.FSE.Common.WindowsManager" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures" isExposed="False"> <Filter entryType="Enumeration" fullName="Tango.FSE.Procedures.ArrayParsingStyle" filterName="ArrayParsingStyle" isExposed="True" /> - <Filter entryType="Class" fullName="Tango.FSE.Procedures.DialogController" filterName="DialogController" isExposed="True" /> <Filter entryType="Interface" fullName="Tango.FSE.Procedures.IDialogController" filterName="IDialogController" isExposed="True" /> <Filter entryType="Interface" fullName="Tango.FSE.Procedures.IProcedureContext" filterName="IProcedureContext" isExposed="True"> <Filter entryType="Method" fullName="Tango.FSE.Procedures.IProcedureContext.BreakPoint" filterName="BreakPoint" isExposed="False" /> @@ -110,13 +110,13 @@ <Filter entryType="Event" fullName="Tango.FSE.Procedures.IProcedureContext.BreakPointRequest" filterName="BreakPointRequest" isExposed="False" /> <Filter entryType="Event" fullName="Tango.FSE.Procedures.IProcedureContext.Progress" filterName="Progress" isExposed="False" /> </Filter> - <Filter entryType="Class" fullName="Tango.FSE.Procedures.ProcedureFailedException" filterName="ProcedureFailedException" isExposed="True" /> <Filter entryType="Class" fullName="Tango.FSE.Procedures.Result" filterName="Result" isExposed="True"> <Filter entryType="Property" fullName="Tango.FSE.Procedures.Result.IsBitmap" filterName="IsBitmap" isExposed="False" /> <Filter entryType="Property" fullName="Tango.FSE.Procedures.Result.IsGraph" filterName="IsGraph" isExposed="False" /> <Filter entryType="Property" fullName="Tango.FSE.Procedures.Result.IsValueArray" filterName="IsValueArray" isExposed="False" /> </Filter> <Filter entryType="Enumeration" fullName="Tango.FSE.Procedures.ResultType" filterName="ResultType" isExposed="True" /> + <Filter entryType="Class" fullName="Tango.FSE.Procedures.UserInput" filterName="UserInput" isExposed="True" /> </Filter> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Contracts" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Controls" isExposed="False" /> @@ -124,20 +124,37 @@ <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.CSV" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Designer" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Dialogs" isExposed="False" /> -<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.Another" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.AddResult" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.Connection" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.Csv" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.Diagnostics" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.RequestUserInputFor" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.Send" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.SendContinuous" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Examples.Sql" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Helpers" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Messages" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.ViewModels" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Views" isExposed="False" /> <Filter entryType="Namespace" fullName="Tango.FSE.Procedures.Windows" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.Information" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.Jobs" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.Logs" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.Performance" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.RemoteUpgrade" isExposed="False" /> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.SQL" isExposed="True"> + <Filter entryType="Class" fullName="Tango.PPC.Shared.SQL.ExecuteSqlRequest" filterName="ExecuteSqlRequest" isExposed="False" /> + <Filter entryType="Class" fullName="Tango.PPC.Shared.SQL.ExecuteSqlResponse" filterName="ExecuteSqlResponse" isExposed="False" /> +</Filter> +<Filter entryType="Namespace" fullName="Tango.PPC.Shared.Updates" isExposed="False" /> <Filter entryType="Namespace" fullName="" isExposed="False" /></ApiFilter> <ProjectSummary>This is the global summery for the help file.</ProjectSummary> <NamespaceSummaries> <NamespaceSummaryItem name="(global)" isDocumented="True">This is the global summery for the help file.</NamespaceSummaryItem> <NamespaceSummaryItem name="Tango.FSE.Procedures" isDocumented="True">Contains a collection of interfaces and classes for the main procedures API.</NamespaceSummaryItem> </NamespaceSummaries> - <ComponentConfigurations /> + <ComponentConfigurations> + </ComponentConfigurations> <MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, TypeParameter, AutoDocumentDispose</MissingTags> </PropertyGroup> <!-- There are no properties for these groups. AnyCPU needs to appear in order for Visual Studio to perform diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ArrayParsingStyle.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ArrayParsingStyle.cs index c829a5b21..e731b7672 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ArrayParsingStyle.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/ArrayParsingStyle.cs @@ -6,9 +6,19 @@ using System.Threading.Tasks; namespace Tango.FSE.Procedures { + /// <summary> + /// Represents an array parsing style. + /// </summary> public enum ArrayParsingStyle { + /// <summary> + /// Formats the array separated by commas. (e.g 1,2,3,4...) + /// </summary> Comma, + + /// <summary> + /// Each item enclosed in square brackets. (e.g [1] [2] [3]...) + /// </summary> SquareBrackets, } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/AddResult/Program.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/AddResult/Program.cs new file mode 100644 index 000000000..750dd436d --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/AddResult/Program.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using Google.Protobuf; +using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.PMR.Stubs; +using Tango.PMR.Diagnostics; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Procedures; + +namespace Tango.FSE.Procedures.Examples.AddResult +{ + #region Example + public class Program + { + public class Person + { + public int Age; + + //Here we use the Description attribute in order to set a custom name + //for this field at the results grid and csv file. + [Description("First Name")] + public String Name; + } + + public void OnExecute(IProcedureContext context) + { + //Add scalar result with the value 10. + context.AddResult(ResultType.Passed, "My Scalar Result", 10); + + //Initialize a list of persons. + List<Person> persons = new List<Person>(); + + for (int i = 0; i < 100; i++) + { + Person p = new Person(); + p.Age = i; + p.Name = "Name " + i; + persons.Add(p); + } + + //Add the persons list as a result. + //This will allow the user to display a grid with all the persons in the list. + //The list can also be exported to CSV. + context.AddResult(ResultType.Passed, "Persons", persons); + + //Here is how we can get only the ages from the persons list and cast them as double values. + List<double> ages = persons.Select(x => x.Age).Cast<double>().ToList(); + + //Adding the ages list as a result.. + context.AddResult(ResultType.Passed, "Ages", ages); + + //Plotting the ages list as a graph result.. + context.AddGraphResult(ResultType.Passed, "Ages Graph", ages); + + + + //And here is how we can draw a completely customized result by drawing a bitmap. + Bitmap bitmap = context.CreateBitmap(400, 200); + Graphics g = context.GetDrawingContext(bitmap); + + //Draw a simple rectangle border. + g.DrawRectangle(Pens.Gray, 0, 0, 399, 199); + + //Draw a diagonal red line across the rectangle. + g.DrawLine(Pens.Red, 0, 0, 400, 200); + + //Draw a string at the center of the rect. + Font font = new Font("Tahoma", 24, FontStyle.Bold, GraphicsUnit.Pixel); + RectangleF layout = new RectangleF(0, 0, 400, 200); + StringFormat format = new StringFormat(); + format.Alignment = StringAlignment.Center; + format.LineAlignment = StringAlignment.Center; + g.DrawString("Bitmap Result", font, Brushes.YellowGreen, layout, format); + + //Dispose the drawing context after you done drawing. + g.Dispose(); + + context.AddBitmapResult(ResultType.Passed, "Bitmap Result", bitmap); + } + } + #endregion +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Connection/Program.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Connection/Program.cs new file mode 100644 index 000000000..63c8cc5f7 --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Connection/Program.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using Google.Protobuf; +using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.PMR.Stubs; +using Tango.PMR.Diagnostics; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Procedures; + +namespace Tango.FSE.Procedures.Examples.Connection +{ + #region Example + public class Program + { + public void OnExecute(IProcedureContext context) + { + + if (context.IsConnected) //Check if there is an active machine connection. + { + if (context.ConnectionType == MachineConnectionTypes.Wifi) //Check enumeration of the active connection type. + { + context.WriteLine("Machine " + context.ConnectedMachine.SerialNumber + " is connected via WiFi."); + } + } + + } + } + #endregion +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Csv/Program.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Csv/Program.cs new file mode 100644 index 000000000..70526436b --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Csv/Program.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using Google.Protobuf; +using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.PMR.Stubs; +using Tango.PMR.Diagnostics; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Procedures; + +namespace Tango.FSE.Procedures.Examples.Csv +{ + #region Example + //CSV Model must contain properties, not plain fields. + public class Person + { + public String Name { get; set; } + public int Age { get; set; } + } + + public class Program + { + //This example demonstrates CSV file writing and reading. + //The csv reading method also accepts a byte array so you can also read a CSV file from resource. + public void OnExecute(IProcedureContext context) + { + //Initialize a list of person model (see Person.csx). + List<Person> persons = new List<Person>(); + + for (int i = 0; i < 100; i++) + { + Person p = new Person(); + p.Name = "Person " + i; + p.Age = i; + persons.Add(p); + } + + //Request the user to choose the csv file location. + String csvFile = context.RequestFileSave("Select CSV File Location", "*.csv", "persons.csv"); + + if (csvFile != null) //Check if user selected a file and pressed 'OK'. + { + //Write the persons list to the selected csv file. + context.WriteCsv<Person>(csvFile, persons); + + //Test csv file for reading... + persons = context.ReadCsv<Person>(csvFile); + + //Write the reading result to the output window. + context.WriteLine(persons); + } + } + } + #endregion +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Diagnostics/Program.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Diagnostics/Program.cs new file mode 100644 index 000000000..c783abaa1 --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Diagnostics/Program.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using Google.Protobuf; +using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.PMR.Stubs; +using Tango.PMR.Diagnostics; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Procedures; + +namespace Tango.FSE.Procedures.Examples.Diagnostics +{ + #region Example + public class Program + { + public void OnExecute(IProcedureContext context) + { + //Initialize a new list of dancer angle values. + List<double> dancerValues = new List<double>(); + + //Collect 100 samples of the middle dancer angle. + for (int i = 0; i < 100; i++) + { + //Get the current diagnostics package. (set 'true' to block the execution until a fresh diagnostics frame arrives) + DiagnosticsPackage package = context.GetDiagnosticsPackage(true); + + //Add the last value in the array to the list. + dancerValues.Add(package.GetMonitorLastValue(TechMonitors.Dancer2Angle)); + } + + //Plot the dancer samples to a graph result. + context.AddGraphResult(ResultType.Passed, "Dancer Angle", dancerValues); + } + } + #endregion +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/RequestUserInputFor/Program.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/RequestUserInputFor/Program.cs new file mode 100644 index 000000000..3ea277bf8 --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/RequestUserInputFor/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using Google.Protobuf; +using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.PMR.Stubs; +using Tango.PMR.Diagnostics; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Procedures; + +namespace Tango.FSE.Procedures.Examples.RequestUserInputFor +{ + #region Example + public enum PersonType + { + Student = 0, + Employee = 1, + Guest = 2 + } + + public class Person + { + public int Age; + public String name; + public bool Activated; + public PersonType Type = PersonType.Employee; + } + + public class Program + { + public void OnExecute(IProcedureContext context) + { + + Person p = context.RequestUserInputFor<Person>("Input Demo", "Please fill in the form"); + context.WriteLine(p); + + } + } + #endregion +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Sql/Program.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Sql/Program.cs new file mode 100644 index 000000000..33072a2dd --- /dev/null +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Examples/Sql/Program.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Drawing; +using Google.Protobuf; +using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.PMR.Stubs; +using Tango.PMR.Diagnostics; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Diagnostics; +using Tango.FSE.Procedures; + +namespace Tango.FSE.Procedures.Examples.Sql +{ + #region Example + //Additional name spaces that are required for working with remote SQL. + using Tango.FSE.Common.SQL; + using Tango.PPC.Shared.SQL; + + public class Program + { + public void OnExecute(IProcedureContext context) + { + + //Get the remote SQL provider from the Tango IOC container. + IRemoteSqlProvider sql = context.GetService<IRemoteSqlProvider>(); + + //Create the SQL command and direct it to affect both the local machine's database and the global Twine's database. + RemoteSqlCommand command = new RemoteSqlCommand(); + command.SQL = "UPDATE MACHINES SET IS_DEMO = 0 WHERE SERIAL_NUMBER = '" + context.ConnectedMachine.SerialNumber + "'"; + command.Mode = RemoteSqlCommandMode.Both; + + //Execute the command... + RemoteSqlCommandResult result = sql.ExecuteSqlCommand(command); + + context.WriteLine("Command executed."); + context.WriteLine("Local Affected Rows " + result.LocalAffectedRecords); + context.WriteLine("Global Affected Rows " + result.GlobalAffectedRecords); + + context.WriteLine(""); + + //Query the machine's database for all the jobs names and length. + command.SQL = "SELECT NAME,LAST_RUN FROM JOBS"; + command.Mode = RemoteSqlCommandMode.Local; + + result = sql.ExecuteSqlCommand(command); + + //Check for errors. + if (!result.HasLocalError) + { + //Write the formatted results set to the console. + context.WriteLine(result.LocalDatSet.ToTableString()); + context.WriteLine(""); + + //Iterate over the data set rows and get values. + foreach (RemoteSqlRow row in result.LocalDatSet.Rows) + { + String name = row.Get<String>("NAME"); + String lastRun = row.Get<String>("LAST_RUN"); + + context.WriteLine(String.Format("NAME {0}, LAST_RUN {1}", name, lastRun)); + } + } + else + { + //Write the error to console. + context.WriteLine(result.LocalError); + } + } + } + #endregion +} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs index 5081e8315..aa5bc4b5d 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/IProcedureContext.cs @@ -45,11 +45,20 @@ namespace Tango.FSE.Procedures /// <summary> /// Adds a new procedure result. + /// The value object can be a primitive, a collection of primitives or a collection of complex types. /// </summary> /// <param name="type">Result type.</param> /// <param name="name">Result title.</param> /// <param name="value">Result value.</param> /// <returns>The result instance.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to add various procedure results. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/AddResult/Program.cs" title="Add results" region="Example" /> + /// </example> Result AddResult(ResultType type, String name, object value); /// <summary> @@ -60,6 +69,14 @@ namespace Tango.FSE.Procedures /// <param name="name">Result title.</param> /// <param name="values">Graph Y axis values.</param> /// <returns>The result instance.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to add various procedure results. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/AddResult/Program.cs" title="Add results" region="Example" /> + /// </example> Result AddGraphResult(ResultType type, String name, IEnumerable<double> values); /// <summary> @@ -71,6 +88,14 @@ namespace Tango.FSE.Procedures /// <param name="name">Result title.</param> /// <param name="bitmap">Result bitmap.</param> /// <returns>The result instance.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to add various procedure results. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/AddResult/Program.cs" title="Add results" region="Example" /> + /// </example> Result AddBitmapResult(ResultType type, String name, Bitmap bitmap); /// <summary> @@ -251,6 +276,14 @@ namespace Tango.FSE.Procedures /// <param name="callback">Specify a callback method to handle the incoming response messages.</param> /// <param name="timeout">First and continuous request timeout in seconds. (leave empty to get the default timeout)</param> /// <param name="args">Request arguments separated by commas.</param> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to send a continuous request to the machine and get a continuous response using the various different SendContinuous method overrides. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/SendContinuous/Program.cs" title="Send a continuous request" region="Example" /> + /// </example> void SendContinuous<T>(String messageName, Action<T> callback, TimeSpan? timeout, params Object[] args) where T : class, IMessage; /// <summary> @@ -263,6 +296,14 @@ namespace Tango.FSE.Procedures /// <param name="messageName">Full or shortened name of the message (e.g "CalculateRequest" or "calculate").</param> /// <param name="callback">Specify a callback method to handle the incoming response messages.</param> /// <param name="args">Request arguments separated by commas.</param> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to send a continuous request to the machine and get a continuous response using the various different SendContinuous method overrides. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/SendContinuous/Program.cs" title="Send a continuous request" region="Example" /> + /// </example> void SendContinuous<T>(String messageName, Action<T> callback, params Object[] args) where T : class, IMessage; /// <summary> @@ -401,6 +442,15 @@ namespace Tango.FSE.Procedures /// <param name="title">The request title.</param> /// <param name="message">The request message.</param> /// <returns>Request result of type T.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to create a model and request the user to fill or modify that model. + /// The model can contain primitives, arrays (comma separated), enums and booleans. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/RequestUserInputFor/Program.cs" title="Request user input" region="Example" /> + /// </example> T RequestUserInputFor<T>(String title, String message); /// <summary> @@ -411,6 +461,15 @@ namespace Tango.FSE.Procedures /// <param name="title">The request title.</param> /// <param name="message">The request message.</param> /// <returns>Request result of type T.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to create a model and request the user to fill or modify that model. + /// The model can contain primitives, arrays (comma separated), enums and booleans. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/RequestUserInputFor/Program.cs" title="Request user input" region="Example" /> + /// </example> T RequestUserInputFor<T>(T model, String title, String message); /// <summary> @@ -440,12 +499,21 @@ namespace Tango.FSE.Procedures void UpdateProgress(String message, bool isIndeterminate = true, double value = 0, double maximum = 100); /// <summary> - /// Gets the latest diagnostics package. + /// Gets the current diagnostics package. /// </summary> - /// <param name="waitForNext">Blocks the execution until a fresh package is available. + /// <param name="waitForNext">Blocks the execution until a fresh diagnostics frame is available. /// This will guarantee that you will get a distinct package each time. /// </param> /// <returns>The diagnostics package.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to collect 100 middle dancer samples and plot them as a graph result. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Diagnostics/Program.cs" title="Collect dancer samples" region="Example" /> + /// </example> + /// <seealso cref="AddGraphResult(ResultType, string, IEnumerable{double})"/> DiagnosticsPackage GetDiagnosticsPackage(bool waitForNext = false); /// <summary> @@ -480,16 +548,40 @@ namespace Tango.FSE.Procedures /// Gets the type of the current machine connection. /// It is recommended to use <see cref="IsConnected"/> before, to ensure active machine connection. /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to check for valid machine connection, check the connection type and get the connected machine's serial number. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Connection/Program.cs" title="Validate connection" region="Example" /> + /// </example> MachineConnectionTypes ConnectionType { get; } /// <summary> /// Gets or sets a value indicating whether a machine is currently connected. /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to check for valid machine connection, check the connection type and get the connected machine's serial number. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Connection/Program.cs" title="Validate connection" region="Example" /> + /// </example> bool IsConnected { get; } /// <summary> /// Gets the currently connected machine entity. /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to check for valid machine connection, check the connection type and get the connected machine's serial number. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Connection/Program.cs" title="Validate connection" region="Example" /> + /// </example> Machine ConnectedMachine { get; } /// <summary> @@ -549,6 +641,15 @@ namespace Tango.FSE.Procedures /// <typeparam name="T">CSV model type.</typeparam> /// <param name="file">The file path.</param> /// <returns>A list containing all the CSV rows as models of type T.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates CSV file writing and reading. + /// The csv reading method also accepts a byte array so you can also read a CSV file from a procedure resource. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Csv/Program.cs" title="Read/Write CSV" region="Example" /> + /// </example> List<T> ReadCsv<T>(String file) where T : class, new(); /// <summary> @@ -558,6 +659,15 @@ namespace Tango.FSE.Procedures /// <typeparam name="T">CSV model type.</typeparam> /// <param name="data">The byte array.</param> /// <returns>A list containing all the CSV rows as models of type T.</returns> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates CSV file writing and reading. + /// The csv reading method also accepts a byte array so you can also read a CSV file from a procedure resource. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Csv/Program.cs" title="Read/Write CSV" region="Example" /> + /// </example> List<T> ReadCsv<T>(byte[] data) where T : class, new(); /// <summary> @@ -567,6 +677,15 @@ namespace Tango.FSE.Procedures /// <typeparam name="T">CSV model type.</typeparam> /// <param name="file">The output file path.</param> /// <param name="items">The items to write.</param> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates CSV file writing and reading. + /// The csv reading method also accepts a byte array so you can also read a CSV file from a procedure resource. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Csv/Program.cs" title="Read/Write CSV" region="Example" /> + /// </example> void WriteCsv<T>(String file, List<T> items); /// <summary> diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/SqlResult.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/SqlResult.cs deleted file mode 100644 index caf4cae32..000000000 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/SqlResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.FSE.Procedures -{ - public class SqlResult - { - public int AffectedRecords { get; set; } - public List<Dictionary<String, Object>> Rows { get; set; } - - public SqlResult() - { - Rows = new List<Dictionary<string, object>>(); - } - } -} diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj index 266fc55aa..b25990012 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Tango.FSE.Procedures.csproj @@ -150,8 +150,14 @@ <DependentUpon>UserInputDialogView.xaml</DependentUpon> </Compile> <Compile Include="Dialogs\UserInputDialogViewVM.cs" /> + <Compile Include="Examples\AddResult\Program.cs" /> + <Compile Include="Examples\Connection\Program.cs" /> + <Compile Include="Examples\Csv\Program.cs" /> + <Compile Include="Examples\Diagnostics\Program.cs" /> + <Compile Include="Examples\RequestUserInputFor\Program.cs" /> <Compile Include="Examples\SendContinuous\Program.cs" /> <Compile Include="Examples\Send\Program.cs" /> + <Compile Include="Examples\Sql\Program.cs" /> <Compile Include="Helpers\ProcedureExceptionHelper.cs" /> <Compile Include="IDialogController.cs" /> <Compile Include="IProcedureContext.cs" /> @@ -171,7 +177,6 @@ <Compile Include="ProcedureFailedException.cs" /> <Compile Include="ProcedureInput.cs" /> <Compile Include="ProcedureProject.cs" /> - <Compile Include="SqlResult.cs" /> <Compile Include="UserInput.cs" /> <Compile Include="ViewModelLocator.cs" /> <Compile Include="ProceduresModule.cs" /> @@ -233,6 +238,10 @@ <EmbeddedResource Include="Resources\procedure_dialog_template.zip" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\..\..\PPC\Tango.PPC.Shared\Tango.PPC.Shared.csproj"> + <Project>{208c8bd8-72c6-4e3c-acaa-351091a2acc7}</Project> + <Name>Tango.PPC.Shared</Name> + </ProjectReference> <ProjectReference Include="..\..\..\Scripting\Tango.Scripting.Basic\Tango.Scripting.Basic.csproj"> <Project>{2b29a699-1d65-463a-8250-a2ce81d019c9}</Project> <Name>Tango.Scripting.Basic</Name> diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml index 661eb5405..46b4058e8 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Themes/Generic.xaml @@ -647,7 +647,7 @@ <material:PackIcon Kind="Cube" VerticalAlignment="Center" Width="12" Height="12" Foreground="{StaticResource FSE_PrimaryForegroundBrush}" /> <DockPanel Margin="2 0 0 0"> <Border BorderThickness="0.5" BorderBrush="{StaticResource FSE_BorderBrush}" Padding="2"> - <TextBlock Margin="0 0 0 0" Width="100" Text="{Binding Name}" ToolTip="{Binding Name}"></TextBlock> + <TextBlock Margin="0 0 0 0" Width="100" Text="{Binding Name}" ToolTip="{Binding Name}" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"></TextBlock> </Border> <DockPanel> <TextBlock DockPanel.Dock="Right" Margin="10 -1 10 0" Foreground="{StaticResource FSE_GrayBrush}" VerticalAlignment="Center"> @@ -655,10 +655,10 @@ <Run Text="{Binding Type}"></Run> <Run>]</Run> </TextBlock> - <Border Margin="0 0 0 0" BorderThickness="0.5" BorderBrush="{StaticResource FSE_BorderBrush}" Padding="2"> + <Border Height="20" Margin="0 0 0 0" BorderThickness="0.5" BorderBrush="{StaticResource FSE_BorderBrush}" Padding="2"> <DockPanel ToolTip="{Binding DisplayValue}"> <material:PackIcon Kind="Search" Width="12" Height="12" VerticalAlignment="Center" /> - <TextBox IsEnabled="{Binding IsEditable}" MinWidth="100" MaxWidth="100" Margin="2 0 0 0" Text="{Binding DisplayValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"> + <TextBox IsEnabled="{Binding IsEditable}" TextWrapping="NoWrap" MinWidth="100" MaxWidth="100" Margin="2 0 0 0" Text="{Binding DisplayValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"> <TextBox.Style> <Style TargetType="TextBox" BasedOn="{StaticResource FSE_Debug_TextBoxStyle}"> <Setter Property="FontWeight" Value="Normal"></Setter> diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/UserInput.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/UserInput.cs index f5d3472d8..043b5bbae 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/UserInput.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/UserInput.cs @@ -6,10 +6,21 @@ using System.Threading.Tasks; namespace Tango.FSE.Procedures { + /// <summary> + /// Can be used to decorate a property or field in order to set their custom name when using <see cref="IProcedureContext.RequestUserInputFor{T}(string, string)"/> + /// </summary> + /// <seealso cref="System.Attribute" /> public class UserInput : Attribute { + /// <summary> + /// Gets or sets the field/property custom name. + /// </summary> public String Name { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="UserInput"/> class. + /// </summary> + /// <param name="name">The field name.</param> public UserInput(String name) { Name = name; diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs index e6857a87b..301c3a25a 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Procedures/Views/ProcedureDesignerView.xaml.cs @@ -224,6 +224,7 @@ namespace Tango.FSE.Procedures.Views public void ResetBreakPointRequest() { GetCurrentEditor().ResetBreakPointLine(); + runTimeBreakPointCanvas.Visibility = Visibility.Hidden; } private void ScriptEditor_BreakPointSymbolPressed(object sender, BreakPointSymbolPressedEventArgs e) diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Diagnostics/DiagnosticsPackage.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Diagnostics/DiagnosticsPackage.cs index d546a8c17..5413dd8ec 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Diagnostics/DiagnosticsPackage.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Diagnostics/DiagnosticsPackage.cs @@ -11,12 +11,15 @@ using Tango.PMR.Diagnostics; namespace Tango.FSE.Common.Diagnostics { + /// <summary> + /// Represents a machine diagnostics frame package. + /// </summary> public class DiagnosticsPackage { private static Dictionary<String, PropertyInfo> _monitorsProperties; /// <summary> - /// Gets or sets the frame. + /// Gets or sets the diagnostics frame. /// </summary> public DiagnosticsFrame Frame { get; set; } @@ -48,21 +51,20 @@ namespace Tango.FSE.Common.Diagnostics } /// <summary> - /// Gets the monitor value by the specified monitor using reflections. + /// Gets the monitor value by the specified <see cref="TechMonitors"/> enumeration. /// </summary> - /// <param name="monitor">The monitor.</param> - /// <returns></returns> + /// <param name="monitor">The monitor enumeration.</param> + /// <returns>The monitor object.</returns> public object GetMonitorObject(TechMonitors monitor) { return MonitorsProperties[monitor.ToString()].GetValue(Frame.Data.Monitors); } /// <summary> - /// Gets the last data point from a protobuf repeated field. + /// Gets the last data point of a repeated field monitor by the specified <see cref="TechMonitors"/> enumeration. /// </summary> - /// <param name="monitor">The monitor.</param> - /// <param name="value">The value.</param> - /// <returns></returns> + /// <param name="monitor">The monitor enumeration.</param> + /// <returns>Last value in the array.</returns> public double GetMonitorLastValue(TechMonitors monitor) { var value = GetMonitorObject(monitor); @@ -71,11 +73,10 @@ namespace Tango.FSE.Common.Diagnostics } /// <summary> - /// Gets the data array from a protobuf repeated field. + /// Gets the data array from a protobuf repeated monitor by the specified <see cref="TechMonitors"/> enumeration. /// </summary> - /// <param name="monitor">The monitor.</param> - /// <param name="value">The value.</param> - /// <returns></returns> + /// <param name="monitor">The monitor enumeration.</param> + /// <returns>List of double values representing the monitor data points.</returns> public List<double> GetMonitorArray(TechMonitors monitor) { var value = GetMonitorObject(monitor); @@ -85,8 +86,8 @@ namespace Tango.FSE.Common.Diagnostics /// <summary> /// Gets the data matrix from a protobuf repeated field of <see cref="DoubleArray"/>. /// </summary> - /// <param name="monitor">The monitor.</param> - /// <returns></returns> + /// <param name="monitor">The monitor enumeration.</param> + /// <returns>A matrix of double values.</returns> public List<List<double>> GetMonitorMatrix(TechMonitors monitor) { var value = GetMonitorObject(monitor); @@ -98,7 +99,7 @@ namespace Tango.FSE.Common.Diagnostics /// Gets the state of the digital interface. /// </summary> /// <param name="interface">The interface.</param> - /// <returns></returns> + /// <returns>Digital interface state object.</returns> public DigitalInterfaceState GetDigitalInterfaceState(TechIos @interface) { return Frame.Data.DigitalInterfaceStates.SingleOrDefault(x => x.InterfaceIO == (InterfaceIOs)@interface); @@ -108,7 +109,7 @@ namespace Tango.FSE.Common.Diagnostics /// Gets the state of the specified valve. /// </summary> /// <param name="valve">The valve.</param> - /// <returns></returns> + /// <returns>Valve state object.</returns> public ValveState GetValveState(TechValves valve) { return Frame.Data.ValvesStates.SingleOrDefault(x => x.ValveType == (ValveType)valve); @@ -118,7 +119,7 @@ namespace Tango.FSE.Common.Diagnostics /// Gets the state of the specified heater. /// </summary> /// <param name="heater">The heater.</param> - /// <returns></returns> + /// <returns>Heater state object.</returns> public HeaterState GetHeaterState(TechHeaters heater) { return Frame.Data.HeatersStates.SingleOrDefault(x => x.HeaterType == (HeaterType)heater); diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/IRemoteSqlProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/IRemoteSqlProvider.cs index d3b6660bf..e41d864c3 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/IRemoteSqlProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/IRemoteSqlProvider.cs @@ -7,10 +7,32 @@ using Tango.PPC.Shared.SQL; namespace Tango.FSE.Common.SQL { + /// <summary> + /// Represents a remote SQL provider. + /// Can be used to execute commands against the remote machine's database and the global twine's database. + /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to set the connected machine's demo state and query for the connected machine's jobs. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Sql/Program.cs" title="Remote SQL" region="Example" /> + /// </example> public interface IRemoteSqlProvider { + /// <summary> + /// Executes the SQL command asynchronously. + /// </summary> + /// <param name="command">The command.</param> + /// <returns>Remote command result.</returns> Task<RemoteSqlCommandResult> ExecuteSqlCommandAsync(RemoteSqlCommand command); + /// <summary> + /// Executes the SQL command. + /// </summary> + /// <param name="command">The command.</param> + /// <returns>Remote command result.</returns> RemoteSqlCommandResult ExecuteSqlCommand(RemoteSqlCommand command); } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommand.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommand.cs index 95f94ad08..99d0f1878 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommand.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommand.cs @@ -6,12 +6,37 @@ using System.Threading.Tasks; namespace Tango.FSE.Common.SQL { + /// <summary> + /// Represents a SQL command that can be executed by a <see cref="IRemoteSqlProvider"/>. + /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to set the connected machine's demo state and query for the connected machine's jobs. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Sql/Program.cs" title="Remote SQL" region="Example" /> + /// </example> public class RemoteSqlCommand { + /// <summary> + /// Gets or sets the command execution mode. + /// </summary> public RemoteSqlCommandMode Mode { get; set; } + + /// <summary> + /// Gets or sets the SQL query. + /// </summary> public String SQL { get; set; } + + /// <summary> + /// Gets or sets the command timeout. + /// </summary> public int Timeout { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlCommand"/> class. + /// </summary> public RemoteSqlCommand() { Timeout = 30; diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandMode.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandMode.cs index 1b05e8e86..702f35e8a 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandMode.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandMode.cs @@ -7,8 +7,16 @@ using System.Threading.Tasks; namespace Tango.FSE.Common.SQL { /// <summary> - /// Represents an SQL command mode. + /// Represents an SQL command execution mode. /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to set the connected machine's demo state and query for the connected machine's jobs. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Sql/Program.cs" title="Remote SQL" region="Example" /> + /// </example> public enum RemoteSqlCommandMode { /// <summary> diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandResult.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandResult.cs index 34ab74a68..da21ae24d 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandResult.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/SQL/RemoteSqlCommandResult.cs @@ -7,22 +7,68 @@ using Tango.PPC.Shared.SQL; namespace Tango.FSE.Common.SQL { + /// <summary> + /// Represents a <see cref="RemoteSqlCommand"/> result. + /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to set the connected machine's demo state and query for the connected machine's jobs. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Sql/Program.cs" title="Remote SQL" region="Example" /> + /// </example> public class RemoteSqlCommandResult { + /// <summary> + /// Gets or sets the connected machine's database affected records. + /// </summary> public int LocalAffectedRecords { get; set; } + + /// <summary> + /// Gets or sets the data set retrieved from the connected machine's database. + /// </summary> public RemoteSqlDataSet LocalDatSet { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the query against the connected machine's database has encountered an error. + /// </summary> public bool HasLocalError { get; set; } + + /// <summary> + /// Gets or sets the error encountered by the query when it was executed against the connected machine's database. + /// Relevant only if <see cref="HasLocalError"/> equals true. + /// </summary> public String LocalError { get; set; } + /// <summary> + /// Gets or sets the global database affected records. + /// </summary> public int GlobalAffectedRecords { get; set; } - public RemoteSqlDataSet GLobalDataSet { get; set; } + + /// <summary> + /// Gets or sets the data set retrieved from the global database. + /// </summary> + public RemoteSqlDataSet GlobalDataSet { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the query against the global database has encountered an error. + /// </summary> public bool HasGlobalError { get; set; } + + /// <summary> + /// Gets or sets the error encountered by the query when it was executed against the global database. + /// Relevant only if <see cref="HasGlobalError"/> equals true. + /// </summary> public String GlobalError { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlCommandResult"/> class. + /// </summary> public RemoteSqlCommandResult() { LocalDatSet = new RemoteSqlDataSet(); - GLobalDataSet = new RemoteSqlDataSet(); + GlobalDataSet = new RemoteSqlDataSet(); } } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/SQL/DefaultRemoteSqlProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/SQL/DefaultRemoteSqlProvider.cs index feac992b3..a657ad55e 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/SQL/DefaultRemoteSqlProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/SQL/DefaultRemoteSqlProvider.cs @@ -159,7 +159,7 @@ namespace Tango.FSE.UI.SQL SqlDataReader reader = await cmd.ExecuteReaderAsync(); result.GlobalAffectedRecords = reader.RecordsAffected; - result.GLobalDataSet = await RemoteSqlDataSet.Load(reader); + result.GlobalDataSet = await RemoteSqlDataSet.Load(reader); } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumn.cs b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumn.cs index 328dbb492..54431bdbe 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumn.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumn.cs @@ -6,19 +6,47 @@ using System.Threading.Tasks; namespace Tango.PPC.Shared.SQL { + /// <summary> + /// Represents a <see cref="RemoteSqlDataSet"/> column. + /// </summary> public class RemoteSqlColumn { + /// <summary> + /// Gets or sets the column name. + /// </summary> public String Name { get; set; } + + /// <summary> + /// Gets or sets the column index. + /// </summary> public int Index { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlColumn"/> class. + /// </summary> public RemoteSqlColumn() { } + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlColumn"/> class. + /// </summary> + /// <param name="name">The column name.</param> public RemoteSqlColumn(String name) { Name = name; } + + /// <summary> + /// Returns a <see cref="System.String" /> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String" /> that represents this instance. + /// </returns> + public override string ToString() + { + return Name; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumnCollection.cs b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumnCollection.cs index 5358e047b..dfda6c3b7 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumnCollection.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlColumnCollection.cs @@ -7,15 +7,27 @@ using System.Threading.Tasks; namespace Tango.PPC.Shared.SQL { + /// <summary> + /// Represents a <see cref="RemoteSqlDataSet"/> columns collection. + /// </summary> + /// <seealso cref="System.Collections.ObjectModel.Collection{Tango.PPC.Shared.SQL.RemoteSqlColumn}" /> public class RemoteSqlColumnCollection : Collection<RemoteSqlColumn> { private Dictionary<String, RemoteSqlColumn> _dictionary; + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlColumnCollection"/> class. + /// </summary> public RemoteSqlColumnCollection() { _dictionary = new Dictionary<string, RemoteSqlColumn>(); } + /// <summary> + /// Inserts an element into the <see cref="T:System.Collections.ObjectModel.Collection`1" /> at the specified index. + /// </summary> + /// <param name="index">The zero-based index at which <paramref name="item" /> should be inserted.</param> + /// <param name="item">The object to insert. The value can be null for reference types.</param> protected override void InsertItem(int index, RemoteSqlColumn item) { item.Index = Count; @@ -23,22 +35,41 @@ namespace Tango.PPC.Shared.SQL base.InsertItem(index, item); } + /// <summary> + /// Removes the element at the specified index of the <see cref="T:System.Collections.ObjectModel.Collection`1" />. + /// </summary> + /// <param name="index">The zero-based index of the element to remove.</param> + /// <exception cref="NotSupportedException"></exception> protected override void RemoveItem(int index) { throw new NotSupportedException(); } + /// <summary> + /// Removes all elements from the <see cref="T:System.Collections.ObjectModel.Collection`1" />. + /// </summary> protected override void ClearItems() { _dictionary.Clear(); base.ClearItems(); } + /// <summary> + /// Replaces the element at the specified index. + /// </summary> + /// <param name="index">The zero-based index of the element to replace.</param> + /// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param> + /// <exception cref="NotSupportedException"></exception> protected override void SetItem(int index, RemoteSqlColumn item) { throw new NotSupportedException(); } + /// <summary> + /// Gets the column index by column name. + /// </summary> + /// <param name="columnName">Column name.</param> + /// <returns></returns> public int GetIndexOf(String columnName) { return _dictionary[columnName].Index; diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlDataSet.cs b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlDataSet.cs index 089908e5a..72b8d2eb2 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlDataSet.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlDataSet.cs @@ -9,17 +9,37 @@ using System.Threading.Tasks; namespace Tango.PPC.Shared.SQL { + /// <summary> + /// Represents remote database query result composed of rows and columns. + /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to set the connected machine's demo state and query for the connected machine's jobs. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Sql/Program.cs" title="Remote SQL" region="Example" /> + /// </example> public class RemoteSqlDataSet { + /// <summary> + /// Gets or sets the dataset columns. + /// </summary> public RemoteSqlColumnCollection Columns { get; set; } private ObservableCollection<RemoteSqlRow> _rows; + /// <summary> + /// Gets or sets the dataset rows. + /// </summary> public ObservableCollection<RemoteSqlRow> Rows { get { return _rows; } set { _rows = value; OnRowsChanged(); } } + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlDataSet"/> class. + /// </summary> public RemoteSqlDataSet() { Columns = new RemoteSqlColumnCollection(); @@ -61,6 +81,11 @@ namespace Tango.PPC.Shared.SQL } } + /// <summary> + /// Creates a new <see cref="RemoteSqlDataSet"/> using the specified <see cref="SqlDataReader"/>. + /// </summary> + /// <param name="reader">The reader.</param> + /// <returns></returns> public static Task<RemoteSqlDataSet> Load(SqlDataReader reader) { return Task.Factory.StartNew<RemoteSqlDataSet>(() => @@ -100,11 +125,21 @@ namespace Tango.PPC.Shared.SQL }); } + /// <summary> + /// Returns a <see cref="System.String" /> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String" /> that represents this instance. + /// </returns> public override string ToString() { return String.Join(", ", Columns.Select(x => x.Name)) + "\n" + String.Join(Environment.NewLine, Rows.Select(x => x.ToString())); } + /// <summary> + /// Formats this dataset as a string with columns and rows. + /// </summary> + /// <returns></returns> public String ToTableString() { Dictionary<int, int> columnsMaxLength = new Dictionary<int, int>(); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlRow.cs b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlRow.cs index bf6b0ba0c..dfabacfea 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlRow.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Shared/SQL/RemoteSqlRow.cs @@ -10,28 +10,61 @@ using Tango.BL.Entities; namespace Tango.PPC.Shared.SQL { + /// <summary> + /// Represents a <see cref="RemoteSqlDataSet"/> row. + /// </summary> + /// <example> + /// <para> + /// <i> + /// The following example demonstrates how to set the connected machine's demo state and query for the connected machine's jobs. + /// </i> + /// </para> + /// <code lang="C#" source="../Tango.FSE.Procedures/Examples/Sql/Program.cs" title="Remote SQL" region="Example" /> + /// </example> public class RemoteSqlRow { private Func<String, Object> _getFuncKey; private Func<int, Object> _getFuncIndex; + /// <summary> + /// Gets or sets the row values. + /// </summary> public List<Object> Values { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="RemoteSqlRow"/> class. + /// </summary> public RemoteSqlRow() { Values = new List<object>(); } + /// <summary> + /// Gets a row value by its column name. + /// </summary> + /// <param name="columnName">Name of the column.</param> + /// <returns>The column value.</returns> public Object Get(String columnName) { return _getFuncKey.Invoke(columnName); } + /// <summary> + /// Gets a row value by its column index. + /// </summary> + /// <param name="columnIndex">Index of the column.</param> + /// <returns>The column value.</returns> public Object Get(int columnIndex) { return _getFuncIndex.Invoke(columnIndex); } + /// <summary> + /// Gets a row value as type T by its column name. + /// </summary> + /// <typeparam name="T">Expected column type.</typeparam> + /// <param name="columnName">Name of the column.</param> + /// <returns>The column value.</returns> public T Get<T>(String columnName) { var value = _getFuncKey.Invoke(columnName); @@ -46,6 +79,12 @@ namespace Tango.PPC.Shared.SQL } } + /// <summary> + /// Gets a row value by its column index. + /// </summary> + /// <typeparam name="T">Expected column type</typeparam> + /// <param name="columnIndex">Index of the column.</param> + /// <returns>The column value.</returns> public T Get<T>(int columnIndex) { var value = _getFuncIndex.Invoke(columnIndex); @@ -66,11 +105,22 @@ namespace Tango.PPC.Shared.SQL _getFuncIndex = getFuncIndex; } + /// <summary> + /// Returns a <see cref="System.String" /> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String" /> that represents this instance. + /// </returns> public override string ToString() { return String.Join(", ", Values); } + /// <summary> + /// Creates an object of type T and maps this row to it based on its properties decorated with <see cref="ColumnAttribute"/>. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <returns></returns> public T Map<T>() where T : class, new() { var obj = Activator.CreateInstance<T>(); @@ -78,6 +128,11 @@ namespace Tango.PPC.Shared.SQL return obj; } + /// <summary> + /// Maps this row to the specified object based on its properties decorated with <see cref="ColumnAttribute"/>. + /// </summary> + /// <typeparam name="T">Model type</typeparam> + /// <param name="obj">The object.</param> public void Map<T>(T obj) where T : class { foreach (var prop in typeof(T).GetPropertiesWithAttribute<ColumnAttribute>()) diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Shared/Tango.PPC.Shared.csproj b/Software/Visual_Studio/PPC/Tango.PPC.Shared/Tango.PPC.Shared.csproj index 10993399c..a0cc9b153 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Shared/Tango.PPC.Shared.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.Shared/Tango.PPC.Shared.csproj @@ -22,6 +22,8 @@ <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <DocumentationFile>..\..\Build\PPC\Debug\Tango.PPC.Shared.xml</DocumentationFile> + <NoWarn>1591</NoWarn> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -30,6 +32,8 @@ <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <DocumentationFile>..\..\Build\PPC\Release\Tango.PPC.Shared.xml</DocumentationFile> + <NoWarn>1591</NoWarn> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs index d2fc9e02b..cd9977520 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/TextEditor.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -24,1121 +25,1229 @@ using Tango.Scripting.Editors.Utils; namespace Tango.Scripting.Editors { - /// <summary> - /// The text editor control. - /// Contains a scrollable TextArea. - /// </summary> - [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)); - } - - /// <summary> - /// Creates a new TextEditor instance. - /// </summary> - public TextEditor() : this(new TextArea()) - { - } - - /// <summary> - /// Creates a new TextEditor instance. - /// </summary> - 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 - - /// <inheritdoc/> - protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() - { - return new TextEditorAutomationPeer(this); - } - - /// Forward focus to TextArea. - /// <inheritdoc/> - protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) - { - base.OnGotKeyboardFocus(e); - if (e.NewFocus == this) { - Keyboard.Focus(this.TextArea); - e.Handled = true; - } - } - - #region Document property - /// <summary> - /// Document property. - /// </summary> - public static readonly DependencyProperty DocumentProperty - = TextView.DocumentProperty.AddOwner( - typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged)); - - /// <summary> - /// Gets/Sets the document displayed by the text editor. - /// This is a dependency property. - /// </summary> - public TextDocument Document { - get { return (TextDocument)GetValue(DocumentProperty); } - set { SetValue(DocumentProperty, value); } - } - - /// <summary> - /// Occurs when the document property has changed. - /// </summary> - public event EventHandler DocumentChanged; - - /// <summary> - /// Raises the <see cref="DocumentChanged"/> event. - /// </summary> - 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 - /// <summary> - /// Options property. - /// </summary> - public static readonly DependencyProperty OptionsProperty - = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged)); - - /// <summary> - /// Gets/Sets the options currently used by the text editor. - /// </summary> - public TextEditorOptions Options { - get { return (TextEditorOptions)GetValue(OptionsProperty); } - set { SetValue(OptionsProperty, value); } - } - - /// <summary> - /// Occurs when a text editor option has changed. - /// </summary> - public event PropertyChangedEventHandler OptionChanged; - - /// <summary> - /// Raises the <see cref="OptionChanged"/> event. - /// </summary> - 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)); - } - - /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/> - 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 - /// <summary> - /// Gets/Sets the text of the current document. - /// </summary> - [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; - } - - /// <summary> - /// Occurs when the Text property changes. - /// </summary> - public event EventHandler TextChanged; - - /// <summary> - /// Raises the <see cref="TextChanged"/> event. - /// </summary> - protected virtual void OnTextChanged(EventArgs e) - { - if (TextChanged != null) { - TextChanged(this, e); - } - } - #endregion - - #region TextArea / ScrollViewer properties - readonly TextArea textArea; - ScrollViewer scrollViewer; - - /// <summary> - /// Is called after the template was applied. - /// </summary> - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this); - } - - /// <summary> - /// Gets the text area. - /// </summary> - public TextArea TextArea { - get { - return textArea; - } - } - - /// <summary> - /// 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. - /// </summary> - 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 - /// <summary> - /// The <see cref="SyntaxHighlighting"/> property. - /// </summary> - public static readonly DependencyProperty SyntaxHighlightingProperty = - DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor), - new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged)); - - - /// <summary> - /// Gets/sets the syntax highlighting definition used to colorize the text. - /// </summary> - 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); - } - } - - /// <summary> - /// Creates the highlighting colorizer for the specified highlighting definition. - /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions. - /// </summary> - /// <returns></returns> - protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) - { - if (highlightingDefinition == null) - throw new ArgumentNullException("highlightingDefinition"); - return new HighlightingColorizer(highlightingDefinition.MainRuleSet); - } - #endregion - - #region WordWrap - /// <summary> - /// Word wrap dependency property. - /// </summary> - public static readonly DependencyProperty WordWrapProperty = - DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False)); - - /// <summary> - /// Specifies whether the text editor uses word wrapping. - /// </summary> - /// <remarks> - /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the - /// HorizontalScrollBarVisibility setting. - /// </remarks> - public bool WordWrap { - get { return (bool)GetValue(WordWrapProperty); } - set { SetValue(WordWrapProperty, Boxes.Box(value)); } - } - #endregion - - #region IsReadOnly - /// <summary> - /// IsReadOnly dependency property. - /// </summary> - public static readonly DependencyProperty IsReadOnlyProperty = - DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged)); - - /// <summary> - /// Specifies whether the user can change the text editor content. - /// Setting this property will replace the - /// <see cref="Editing.TextArea.ReadOnlySectionProvider">TextArea.ReadOnlySectionProvider</see>. - /// </summary> - 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 - /// <summary> - /// Dependency property for <see cref="IsModified"/> - /// </summary> - public static readonly DependencyProperty IsModifiedProperty = - DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged)); - - /// <summary> - /// Gets/Sets the 'modified' flag. - /// </summary> - 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 - /// <summary> - /// ShowLineNumbers dependency property. - /// </summary> - public static readonly DependencyProperty ShowLineNumbersProperty = - DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor), - new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged)); - - /// <summary> - /// Specifies whether line numbers are shown on the left to the text view. - /// </summary> - 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 - /// <summary> - /// LineNumbersForeground dependency property. - /// </summary> - public static readonly DependencyProperty LineNumbersForegroundProperty = - DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor), - new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged)); - - /// <summary> - /// Gets/sets the Brush used for displaying the foreground color of line numbers. - /// </summary> - 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 - /// <summary> - /// Appends text to the end of the document. - /// </summary> - public void AppendText(string textData) - { - var document = GetDocument(); - document.Insert(document.TextLength, textData); - } - - /// <summary> - /// Begins a group of document changes. - /// </summary> - public void BeginChange() - { - GetDocument().BeginUpdate(); - } - - /// <summary> - /// Copies the current selection to the clipboard. - /// </summary> - public void Copy() - { - Execute(ApplicationCommands.Copy); - } - - /// <summary> - /// Removes the current selection and copies it to the clipboard. - /// </summary> - public void Cut() - { - Execute(ApplicationCommands.Cut); - } - - /// <summary> - /// Begins a group of document changes and returns an object that ends the group of document - /// changes when it is disposed. - /// </summary> - public IDisposable DeclareChangeBlock() - { - return GetDocument().RunUpdate(); - } - - /// <summary> - /// Ends the current group of document changes. - /// </summary> - public void EndChange() - { - GetDocument().EndUpdate(); - } - - /// <summary> - /// Scrolls one line down. - /// </summary> - public void LineDown() - { - if (scrollViewer != null) - scrollViewer.LineDown(); - } - - /// <summary> - /// Scrolls to the left. - /// </summary> - public void LineLeft() - { - if (scrollViewer != null) - scrollViewer.LineLeft(); - } - - /// <summary> - /// Scrolls to the right. - /// </summary> - public void LineRight() - { - if (scrollViewer != null) - scrollViewer.LineRight(); - } - - /// <summary> - /// Scrolls one line up. - /// </summary> - public void LineUp() - { - if (scrollViewer != null) - scrollViewer.LineUp(); - } - - /// <summary> - /// Scrolls one page down. - /// </summary> - public void PageDown() - { - if (scrollViewer != null) - scrollViewer.PageDown(); - } - - /// <summary> - /// Scrolls one page up. - /// </summary> - public void PageUp() - { - if (scrollViewer != null) - scrollViewer.PageUp(); - } - - /// <summary> - /// Scrolls one page left. - /// </summary> - public void PageLeft() - { - if (scrollViewer != null) - scrollViewer.PageLeft(); - } - - /// <summary> - /// Scrolls one page right. - /// </summary> - public void PageRight() - { - if (scrollViewer != null) - scrollViewer.PageRight(); - } - - /// <summary> - /// Pastes the clipboard content. - /// </summary> - public void Paste() - { - Execute(ApplicationCommands.Paste); - } - - /// <summary> - /// Redoes the most recent undone command. - /// </summary> - /// <returns>True is the redo operation was successful, false is the redo stack is empty.</returns> - public bool Redo() - { - if (CanExecute(ApplicationCommands.Redo)) { - Execute(ApplicationCommands.Redo); - return true; - } - return false; - } - - /// <summary> - /// Scrolls to the end of the document. - /// </summary> - public void ScrollToEnd() - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToEnd(); - } - - /// <summary> - /// Scrolls to the start of the document. - /// </summary> - public void ScrollToHome() - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToHome(); - } - - /// <summary> - /// Scrolls to the specified position in the document. - /// </summary> - public void ScrollToHorizontalOffset(double offset) - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToHorizontalOffset(offset); - } - - /// <summary> - /// Scrolls to the specified position in the document. - /// </summary> - public void ScrollToVerticalOffset(double offset) - { - ApplyTemplate(); // ensure scrollViewer is created - if (scrollViewer != null) - scrollViewer.ScrollToVerticalOffset(offset); - } - - /// <summary> - /// Selects the entire text. - /// </summary> - public void SelectAll() - { - Execute(ApplicationCommands.SelectAll); - } - - /// <summary> - /// Undoes the most recent command. - /// </summary> - /// <returns>True is the undo operation was successful, false is the undo stack is empty.</returns> - public bool Undo() - { - if (CanExecute(ApplicationCommands.Undo)) { - Execute(ApplicationCommands.Undo); - return true; - } - return false; - } - - /// <summary> - /// Gets if the most recent undone command can be redone. - /// </summary> - public bool CanRedo { - get { return CanExecute(ApplicationCommands.Redo); } - } - - /// <summary> - /// Gets if the most recent command can be undone. - /// </summary> - public bool CanUndo { - get { return CanExecute(ApplicationCommands.Undo); } - } - - /// <summary> - /// Gets the vertical size of the document. - /// </summary> - public double ExtentHeight { - get { - return scrollViewer != null ? scrollViewer.ExtentHeight : 0; - } - } - - /// <summary> - /// Gets the horizontal size of the current document region. - /// </summary> - public double ExtentWidth { - get { - return scrollViewer != null ? scrollViewer.ExtentWidth : 0; - } - } - - /// <summary> - /// Gets the horizontal size of the viewport. - /// </summary> - public double ViewportHeight { - get { - return scrollViewer != null ? scrollViewer.ViewportHeight : 0; - } - } - - /// <summary> - /// Gets the horizontal size of the viewport. - /// </summary> - public double ViewportWidth { - get { - return scrollViewer != null ? scrollViewer.ViewportWidth : 0; - } - } - - /// <summary> - /// Gets the vertical scroll position. - /// </summary> - public double VerticalOffset { - get { - return scrollViewer != null ? scrollViewer.VerticalOffset : 0; - } - } - - /// <summary> - /// Gets the horizontal scroll position. - /// </summary> - public double HorizontalOffset { - get { - return scrollViewer != null ? scrollViewer.HorizontalOffset : 0; - } - } - #endregion - - #region TextBox methods - /// <summary> - /// Gets/Sets the selected text. - /// </summary> - [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); - } - } - } - - /// <summary> - /// Gets/sets the caret position. - /// </summary> - [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; - } - } - - /// <summary> - /// Gets/sets the start position of the selection. - /// </summary> - [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); - } - } - - /// <summary> - /// Gets/sets the length of the selection. - /// </summary> - [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); - } - } - - /// <summary> - /// Selects the specified text section. - /// </summary> - 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; - } - - /// <summary> - /// Gets the number of lines in the document. - /// </summary> - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int LineCount { - get { - TextDocument document = this.Document; - if (document != null) - return document.LineCount; - else - return 1; - } - } - - /// <summary> - /// Clears the text. - /// </summary> - public void Clear() - { - this.Text = string.Empty; - } - #endregion - - #region Loading from stream - /// <summary> - /// Loads the text from the stream, auto-detecting the encoding. - /// </summary> - /// <remarks> - /// This method sets <see cref="IsModified"/> to false. - /// </remarks> - 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; - } - - /// <summary> - /// Loads the text from the stream, auto-detecting the encoding. - /// </summary> - 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); - } - } - - /// <summary> - /// Gets/sets the encoding used when the file is saved. - /// </summary> - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public Encoding Encoding { get; set; } - - /// <summary> - /// Saves the text to the stream. - /// </summary> - /// <remarks> - /// This method sets <see cref="IsModified"/> to false. - /// </remarks> - 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; - } - - /// <summary> - /// Saves the text to the file. - /// </summary> - 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 - /// <summary> - /// The PreviewMouseHover event. - /// </summary> - public static readonly RoutedEvent PreviewMouseHoverEvent = - TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); - - /// <summary> - /// The MouseHover event. - /// </summary> - public static readonly RoutedEvent MouseHoverEvent = - TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); - - - /// <summary> - /// The PreviewMouseHoverStopped event. - /// </summary> - public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = - TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); - - /// <summary> - /// The MouseHoverStopped event. - /// </summary> - public static readonly RoutedEvent MouseHoverStoppedEvent = - TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); - - - /// <summary> - /// Occurs when the mouse has hovered over a fixed location for some time. - /// </summary> - public event MouseEventHandler PreviewMouseHover { - add { AddHandler(PreviewMouseHoverEvent, value); } - remove { RemoveHandler(PreviewMouseHoverEvent, value); } - } - - /// <summary> - /// Occurs when the mouse has hovered over a fixed location for some time. - /// </summary> - public event MouseEventHandler MouseHover { - add { AddHandler(MouseHoverEvent, value); } - remove { RemoveHandler(MouseHoverEvent, value); } - } - - /// <summary> - /// Occurs when the mouse had previously hovered but now started moving again. - /// </summary> - public event MouseEventHandler PreviewMouseHoverStopped { - add { AddHandler(PreviewMouseHoverStoppedEvent, value); } - remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } - } - - /// <summary> - /// Occurs when the mouse had previously hovered but now started moving again. - /// </summary> - public event MouseEventHandler MouseHoverStopped { - add { AddHandler(MouseHoverStoppedEvent, value); } - remove { RemoveHandler(MouseHoverStoppedEvent, value); } - } - #endregion - - #region ScrollBarVisibility - /// <summary> - /// Dependency property for <see cref="HorizontalScrollBarVisibility"/> - /// </summary> - public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); - - /// <summary> - /// Gets/Sets the horizontal scroll bar visibility. - /// </summary> - public ScrollBarVisibility HorizontalScrollBarVisibility { - get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); } - set { SetValue(HorizontalScrollBarVisibilityProperty, value); } - } - - /// <summary> - /// Dependency property for <see cref="VerticalScrollBarVisibility"/> - /// </summary> - public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); - - /// <summary> - /// Gets/Sets the vertical scroll bar visibility. - /// </summary> - public ScrollBarVisibility VerticalScrollBarVisibility { - get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); } - set { SetValue(VerticalScrollBarVisibilityProperty, value); } - } - #endregion - - object IServiceProvider.GetService(Type serviceType) - { - return textArea.GetService(serviceType); - } - - /// <summary> - /// Gets the text view position from a point inside the editor. - /// </summary> - /// <param name="point">The position, relative to top left - /// corner of TextEditor control</param> - /// <returns>The text view position, or null if the point is outside the document.</returns> - public TextViewPosition? GetPositionFromPoint(Point point) - { - if (this.Document == null) - return null; - TextView textView = this.TextArea.TextView; - return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset); - } - - /// <summary> - /// Scrolls to the specified line. - /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). - /// </summary> - public void ScrollToLine(int line) - { - ScrollTo(line, -1); - } - - /// <summary> - /// Scrolls to the specified line/column. - /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). - /// </summary> - 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); - } - } - } - } - } + /// <summary> + /// The text editor control. + /// Contains a scrollable TextArea. + /// </summary> + [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)); + } + + /// <summary> + /// Creates a new TextEditor instance. + /// </summary> + public TextEditor() : this(new TextArea()) + { + } + + /// <summary> + /// Creates a new TextEditor instance. + /// </summary> + 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 + + /// <inheritdoc/> + protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() + { + return new TextEditorAutomationPeer(this); + } + + /// Forward focus to TextArea. + /// <inheritdoc/> + protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + base.OnGotKeyboardFocus(e); + if (e.NewFocus == this) + { + Keyboard.Focus(this.TextArea); + e.Handled = true; + } + } + + #region Document property + /// <summary> + /// Document property. + /// </summary> + public static readonly DependencyProperty DocumentProperty + = TextView.DocumentProperty.AddOwner( + typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged)); + + /// <summary> + /// Gets/Sets the document displayed by the text editor. + /// This is a dependency property. + /// </summary> + public TextDocument Document + { + get { return (TextDocument)GetValue(DocumentProperty); } + set { SetValue(DocumentProperty, value); } + } + + /// <summary> + /// Occurs when the document property has changed. + /// </summary> + public event EventHandler DocumentChanged; + + /// <summary> + /// Raises the <see cref="DocumentChanged"/> event. + /// </summary> + 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 + /// <summary> + /// Options property. + /// </summary> + public static readonly DependencyProperty OptionsProperty + = TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged)); + + /// <summary> + /// Gets/Sets the options currently used by the text editor. + /// </summary> + public TextEditorOptions Options + { + get { return (TextEditorOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } + + /// <summary> + /// Occurs when a text editor option has changed. + /// </summary> + public event PropertyChangedEventHandler OptionChanged; + + /// <summary> + /// Raises the <see cref="OptionChanged"/> event. + /// </summary> + 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)); + } + + /// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/> + 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 + /// <summary> + /// Gets/Sets the text of the current document. + /// </summary> + [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; + } + + /// <summary> + /// Occurs when the Text property changes. + /// </summary> + public event EventHandler TextChanged; + + /// <summary> + /// Raises the <see cref="TextChanged"/> event. + /// </summary> + protected virtual void OnTextChanged(EventArgs e) + { + if (TextChanged != null) + { + TextChanged(this, e); + } + } + #endregion + + #region TextArea / ScrollViewer properties + readonly TextArea textArea; + ScrollViewer scrollViewer; + + /// <summary> + /// Is called after the template was applied. + /// </summary> + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this); + } + + /// <summary> + /// Gets the text area. + /// </summary> + public TextArea TextArea + { + get + { + return textArea; + } + } + + /// <summary> + /// 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. + /// </summary> + 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 + /// <summary> + /// The <see cref="SyntaxHighlighting"/> property. + /// </summary> + public static readonly DependencyProperty SyntaxHighlightingProperty = + DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor), + new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged)); + + + /// <summary> + /// Gets/sets the syntax highlighting definition used to colorize the text. + /// </summary> + 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); + } + } + + /// <summary> + /// Creates the highlighting colorizer for the specified highlighting definition. + /// Allows derived classes to provide custom colorizer implementations for special highlighting definitions. + /// </summary> + /// <returns></returns> + protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) + { + if (highlightingDefinition == null) + throw new ArgumentNullException("highlightingDefinition"); + return new HighlightingColorizer(highlightingDefinition.MainRuleSet); + } + #endregion + + #region WordWrap + /// <summary> + /// Word wrap dependency property. + /// </summary> + public static readonly DependencyProperty WordWrapProperty = + DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False)); + + /// <summary> + /// Specifies whether the text editor uses word wrapping. + /// </summary> + /// <remarks> + /// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the + /// HorizontalScrollBarVisibility setting. + /// </remarks> + public bool WordWrap + { + get { return (bool)GetValue(WordWrapProperty); } + set { SetValue(WordWrapProperty, Boxes.Box(value)); } + } + #endregion + + #region IsReadOnly + /// <summary> + /// IsReadOnly dependency property. + /// </summary> + public static readonly DependencyProperty IsReadOnlyProperty = + DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged)); + + /// <summary> + /// Specifies whether the user can change the text editor content. + /// Setting this property will replace the + /// <see cref="Editing.TextArea.ReadOnlySectionProvider">TextArea.ReadOnlySectionProvider</see>. + /// </summary> + 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 + /// <summary> + /// Dependency property for <see cref="IsModified"/> + /// </summary> + public static readonly DependencyProperty IsModifiedProperty = + DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged)); + + /// <summary> + /// Gets/Sets the 'modified' flag. + /// </summary> + 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 + /// <summary> + /// ShowLineNumbers dependency property. + /// </summary> + public static readonly DependencyProperty ShowLineNumbersProperty = + DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor), + new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged)); + + /// <summary> + /// Specifies whether line numbers are shown on the left to the text view. + /// </summary> + 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 + /// <summary> + /// LineNumbersForeground dependency property. + /// </summary> + public static readonly DependencyProperty LineNumbersForegroundProperty = + DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor), + new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged)); + + /// <summary> + /// Gets/sets the Brush used for displaying the foreground color of line numbers. + /// </summary> + 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 + /// <summary> + /// Appends text to the end of the document. + /// </summary> + public void AppendText(string textData) + { + var document = GetDocument(); + document.Insert(document.TextLength, textData); + } + + /// <summary> + /// Begins a group of document changes. + /// </summary> + public void BeginChange() + { + GetDocument().BeginUpdate(); + } + + /// <summary> + /// Copies the current selection to the clipboard. + /// </summary> + public void Copy() + { + Execute(ApplicationCommands.Copy); + } + + /// <summary> + /// Removes the current selection and copies it to the clipboard. + /// </summary> + public void Cut() + { + Execute(ApplicationCommands.Cut); + } + + /// <summary> + /// Begins a group of document changes and returns an object that ends the group of document + /// changes when it is disposed. + /// </summary> + public IDisposable DeclareChangeBlock() + { + return GetDocument().RunUpdate(); + } + + /// <summary> + /// Ends the current group of document changes. + /// </summary> + public void EndChange() + { + GetDocument().EndUpdate(); + } + + /// <summary> + /// Scrolls one line down. + /// </summary> + public void LineDown() + { + if (scrollViewer != null) + scrollViewer.LineDown(); + } + + /// <summary> + /// Scrolls to the left. + /// </summary> + public void LineLeft() + { + if (scrollViewer != null) + scrollViewer.LineLeft(); + } + + /// <summary> + /// Scrolls to the right. + /// </summary> + public void LineRight() + { + if (scrollViewer != null) + scrollViewer.LineRight(); + } + + /// <summary> + /// Scrolls one line up. + /// </summary> + public void LineUp() + { + if (scrollViewer != null) + scrollViewer.LineUp(); + } + + /// <summary> + /// Scrolls one page down. + /// </summary> + public void PageDown() + { + if (scrollViewer != null) + scrollViewer.PageDown(); + } + + /// <summary> + /// Scrolls one page up. + /// </summary> + public void PageUp() + { + if (scrollViewer != null) + scrollViewer.PageUp(); + } + + /// <summary> + /// Scrolls one page left. + /// </summary> + public void PageLeft() + { + if (scrollViewer != null) + scrollViewer.PageLeft(); + } + + /// <summary> + /// Scrolls one page right. + /// </summary> + public void PageRight() + { + if (scrollViewer != null) + scrollViewer.PageRight(); + } + + /// <summary> + /// Pastes the clipboard content. + /// </summary> + public void Paste() + { + Execute(ApplicationCommands.Paste); + } + + /// <summary> + /// Redoes the most recent undone command. + /// </summary> + /// <returns>True is the redo operation was successful, false is the redo stack is empty.</returns> + public bool Redo() + { + if (CanExecute(ApplicationCommands.Redo)) + { + Execute(ApplicationCommands.Redo); + return true; + } + return false; + } + + /// <summary> + /// Scrolls to the end of the document. + /// </summary> + public void ScrollToEnd() + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToEnd(); + } + + /// <summary> + /// Scrolls to the start of the document. + /// </summary> + public void ScrollToHome() + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToHome(); + } + + /// <summary> + /// Scrolls to the specified position in the document. + /// </summary> + public void ScrollToHorizontalOffset(double offset) + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToHorizontalOffset(offset); + } + + /// <summary> + /// Scrolls to the specified position in the document. + /// </summary> + public void ScrollToVerticalOffset(double offset) + { + ApplyTemplate(); // ensure scrollViewer is created + if (scrollViewer != null) + scrollViewer.ScrollToVerticalOffset(offset); + } + + /// <summary> + /// Selects the entire text. + /// </summary> + public void SelectAll() + { + Execute(ApplicationCommands.SelectAll); + } + + /// <summary> + /// Undoes the most recent command. + /// </summary> + /// <returns>True is the undo operation was successful, false is the undo stack is empty.</returns> + public bool Undo() + { + if (CanExecute(ApplicationCommands.Undo)) + { + Execute(ApplicationCommands.Undo); + return true; + } + return false; + } + + /// <summary> + /// Gets if the most recent undone command can be redone. + /// </summary> + public bool CanRedo + { + get { return CanExecute(ApplicationCommands.Redo); } + } + + /// <summary> + /// Gets if the most recent command can be undone. + /// </summary> + public bool CanUndo + { + get { return CanExecute(ApplicationCommands.Undo); } + } + + /// <summary> + /// Gets the vertical size of the document. + /// </summary> + public double ExtentHeight + { + get + { + return scrollViewer != null ? scrollViewer.ExtentHeight : 0; + } + } + + /// <summary> + /// Gets the horizontal size of the current document region. + /// </summary> + public double ExtentWidth + { + get + { + return scrollViewer != null ? scrollViewer.ExtentWidth : 0; + } + } + + /// <summary> + /// Gets the horizontal size of the viewport. + /// </summary> + public double ViewportHeight + { + get + { + return scrollViewer != null ? scrollViewer.ViewportHeight : 0; + } + } + + /// <summary> + /// Gets the horizontal size of the viewport. + /// </summary> + public double ViewportWidth + { + get + { + return scrollViewer != null ? scrollViewer.ViewportWidth : 0; + } + } + + /// <summary> + /// Gets the vertical scroll position. + /// </summary> + public double VerticalOffset + { + get + { + return scrollViewer != null ? scrollViewer.VerticalOffset : 0; + } + } + + /// <summary> + /// Gets the horizontal scroll position. + /// </summary> + public double HorizontalOffset + { + get + { + return scrollViewer != null ? scrollViewer.HorizontalOffset : 0; + } + } + #endregion + + #region TextBox methods + /// <summary> + /// Gets/Sets the selected text. + /// </summary> + [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); + } + } + } + + /// <summary> + /// Gets/sets the caret position. + /// </summary> + [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; + } + } + + /// <summary> + /// Gets/sets the start position of the selection. + /// </summary> + [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); + } + } + + /// <summary> + /// Gets/sets the length of the selection. + /// </summary> + [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); + } + } + + /// <summary> + /// Selects the specified text section. + /// </summary> + public void Select(int start, int length) + { + int documentLength = Document != null ? Document.TextLength : 0; + + if (start < 0 || start > documentLength) + { + Debug.WriteLine(new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength)); + return; + } + + if (length < 0 || start + length > documentLength) + { + Debug.WriteLine(new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - length))); + return; + } + + textArea.Selection = SimpleSelection.Create(textArea, start, start + length); + textArea.Caret.Offset = start + length; + } + + /// <summary> + /// Gets the number of lines in the document. + /// </summary> + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int LineCount + { + get + { + TextDocument document = this.Document; + if (document != null) + return document.LineCount; + else + return 1; + } + } + + /// <summary> + /// Clears the text. + /// </summary> + public void Clear() + { + this.Text = string.Empty; + } + #endregion + + #region Loading from stream + /// <summary> + /// Loads the text from the stream, auto-detecting the encoding. + /// </summary> + /// <remarks> + /// This method sets <see cref="IsModified"/> to false. + /// </remarks> + 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; + } + + /// <summary> + /// Loads the text from the stream, auto-detecting the encoding. + /// </summary> + 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); + } + } + + /// <summary> + /// Gets/sets the encoding used when the file is saved. + /// </summary> + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Encoding Encoding { get; set; } + + /// <summary> + /// Saves the text to the stream. + /// </summary> + /// <remarks> + /// This method sets <see cref="IsModified"/> to false. + /// </remarks> + 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; + } + + /// <summary> + /// Saves the text to the file. + /// </summary> + 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 + /// <summary> + /// The PreviewMouseHover event. + /// </summary> + public static readonly RoutedEvent PreviewMouseHoverEvent = + TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor)); + + /// <summary> + /// The MouseHover event. + /// </summary> + public static readonly RoutedEvent MouseHoverEvent = + TextView.MouseHoverEvent.AddOwner(typeof(TextEditor)); + + + /// <summary> + /// The PreviewMouseHoverStopped event. + /// </summary> + public static readonly RoutedEvent PreviewMouseHoverStoppedEvent = + TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + /// <summary> + /// The MouseHoverStopped event. + /// </summary> + public static readonly RoutedEvent MouseHoverStoppedEvent = + TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor)); + + + /// <summary> + /// Occurs when the mouse has hovered over a fixed location for some time. + /// </summary> + public event MouseEventHandler PreviewMouseHover + { + add { AddHandler(PreviewMouseHoverEvent, value); } + remove { RemoveHandler(PreviewMouseHoverEvent, value); } + } + + /// <summary> + /// Occurs when the mouse has hovered over a fixed location for some time. + /// </summary> + public event MouseEventHandler MouseHover + { + add { AddHandler(MouseHoverEvent, value); } + remove { RemoveHandler(MouseHoverEvent, value); } + } + + /// <summary> + /// Occurs when the mouse had previously hovered but now started moving again. + /// </summary> + public event MouseEventHandler PreviewMouseHoverStopped + { + add { AddHandler(PreviewMouseHoverStoppedEvent, value); } + remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); } + } + + /// <summary> + /// Occurs when the mouse had previously hovered but now started moving again. + /// </summary> + public event MouseEventHandler MouseHoverStopped + { + add { AddHandler(MouseHoverStoppedEvent, value); } + remove { RemoveHandler(MouseHoverStoppedEvent, value); } + } + #endregion + + #region ScrollBarVisibility + /// <summary> + /// Dependency property for <see cref="HorizontalScrollBarVisibility"/> + /// </summary> + public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); + + /// <summary> + /// Gets/Sets the horizontal scroll bar visibility. + /// </summary> + public ScrollBarVisibility HorizontalScrollBarVisibility + { + get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); } + set { SetValue(HorizontalScrollBarVisibilityProperty, value); } + } + + /// <summary> + /// Dependency property for <see cref="VerticalScrollBarVisibility"/> + /// </summary> + public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible)); + + /// <summary> + /// Gets/Sets the vertical scroll bar visibility. + /// </summary> + public ScrollBarVisibility VerticalScrollBarVisibility + { + get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); } + set { SetValue(VerticalScrollBarVisibilityProperty, value); } + } + #endregion + + object IServiceProvider.GetService(Type serviceType) + { + return textArea.GetService(serviceType); + } + + /// <summary> + /// Gets the text view position from a point inside the editor. + /// </summary> + /// <param name="point">The position, relative to top left + /// corner of TextEditor control</param> + /// <returns>The text view position, or null if the point is outside the document.</returns> + public TextViewPosition? GetPositionFromPoint(Point point) + { + if (this.Document == null) + return null; + TextView textView = this.TextArea.TextView; + return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset); + } + + /// <summary> + /// Scrolls to the specified line. + /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). + /// </summary> + public void ScrollToLine(int line) + { + ScrollTo(line, -1); + } + + /// <summary> + /// Scrolls to the specified line/column. + /// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior). + /// </summary> + 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 |
