##// END OF EJS Templates
Added the time delta column to the interactive trace listener
cin -
r214:9c32ef39b851 v2
parent child
Show More
@@ -1,122 +1,123
1 1 using Implab.Parallels;
2 2 using System;
3 3 using System.Collections.Generic;
4 4 using System.Linq;
5 5 using System.Text;
6 6 using System.Threading;
7 7 using System.Threading.Tasks;
8 8 using System.Windows.Forms;
9 9
10 10 namespace Implab.Diagnostics.Interactive
11 11 {
12 12 public class InteractiveListener: ListenerBase
13 13 {
14 14 TraceForm m_form;
15 15
16 16 SynchronizationContext m_syncGuiThread;
17 17 readonly Promise m_guiStarted = new Promise();
18 18
19 19 readonly IPromise m_guiFinished;
20 20 // readonly IPromise m_workerFinished = new Promise<object>();
21 21
22 22 readonly MTQueue<TraceViewItem> m_queue = new MTQueue<TraceViewItem>();
23 23 readonly AutoResetEvent m_queueEvent = new AutoResetEvent(false);
24 24
25 25 int m_queueLength;
26 26 bool m_exitPending;
27 27
28 28 readonly object m_pauseLock = new object();
29 29 bool m_paused;
30 30 readonly ManualResetEvent m_pauseEvent = new ManualResetEvent(true);
31 31
32 32 public InteractiveListener() {
33 33 m_guiFinished = AsyncPool.RunThread(GuiThread);
34 34 /*m_workerFinished = */AsyncPool.RunThread(QueueThread);
35 35
36 36 m_guiStarted.Join();
37 37 }
38 38
39 39 void GuiThread() {
40 40 m_form = new TraceForm(); // will create SynchronizationContext
41 41
42 42 m_form.PauseEvents += (s,a) => Pause();
43 43 m_form.ResumeEvents += (s, a) => Resume();
44 44
45 45 m_syncGuiThread = SynchronizationContext.Current;
46 46 m_guiStarted.Resolve();
47 47 Application.Run();
48 48 }
49 49
50 50 void QueueThread() {
51 51 while (!m_exitPending) {
52 52 if (m_paused)
53 53 m_pauseEvent.WaitOne();
54 54
55 55 TraceViewItem item;
56 56 if (m_queue.TryDequeue(out item)) {
57 57 Interlocked.Decrement(ref m_queueLength);
58 58
59 m_syncGuiThread.Send(x => m_form.AddTraceEvent(item),null);
59 m_syncGuiThread.Post(x => m_form.AddTraceEvent(item),null);
60 60 } else {
61 61 m_queueEvent.WaitOne();
62 62 }
63 63 }
64 64 }
65 65
66 66 public void Pause() {
67 67 // for consistency we need to set this properties atomically
68 68 lock (m_pauseLock) {
69 69 m_pauseEvent.Reset();
70 70 m_paused = true;
71 71 }
72 72 }
73 73
74 74 public void Resume() {
75 75 // for consistency we need to set this properties atomically
76 76 lock (m_pauseLock) {
77 77 m_paused = false;
78 78 m_pauseEvent.Set();
79 79 }
80 80 }
81 81
82 82 void Enqueue(TraceViewItem item) {
83 83 m_queue.Enqueue(item);
84 84 if (Interlocked.Increment(ref m_queueLength) == 1)
85 85 m_queueEvent.Set();
86 86 }
87 87
88 88 public void ShowForm() {
89 89 m_syncGuiThread.Post(x => m_form.Show(), null);
90 90 }
91 91
92 92 public void HideForm() {
93 93 m_syncGuiThread.Post(x => m_form.Hide(), null);
94 94 }
95 95
96 96 void Terminate() {
97 97 m_exitPending = true;
98 98 Resume();
99 99 m_syncGuiThread.Post(x => Application.ExitThread(), null);
100 100 }
101 101
102 102 protected override void Dispose(bool disposing) {
103 103 if (disposing) {
104 104 Terminate();
105 105 m_guiFinished.Join();
106 106 }
107 107 base.Dispose(disposing);
108 108 }
109 109
110 110 public override void Write(LogEventArgs args, object entry) {
111 111 var item = new TraceViewItem {
112 112 Indent = args.Operation.Level,
113 113 Message = entry.ToString(),
114 114 Thread = args.ThreadId,
115 115 Channel = args.Channel.ToString(),
116 Timestamp = Environment.TickCount
116 Timestamp = Environment.TickCount,
117 TimeDelta = args.OperationTimeOffset
117 118 };
118 119
119 120 Enqueue(item);
120 121 }
121 122 }
122 123 }
@@ -1,134 +1,149
1 1 namespace Implab.Diagnostics.Interactive {
2 2 partial class TraceForm {
3 3 /// <summary>
4 4 /// Required designer variable.
5 5 /// </summary>
6 6 private System.ComponentModel.IContainer components = null;
7 7
8 8 /// <summary>
9 9 /// Clean up any resources being used.
10 10 /// </summary>
11 11 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
12 12 protected override void Dispose(bool disposing) {
13 13 if (disposing && (components != null)) {
14 14 components.Dispose();
15 15 }
16 16 base.Dispose(disposing);
17 17 }
18 18
19 19 #region Windows Form Designer generated code
20 20
21 21 /// <summary>
22 22 /// Required method for Designer support - do not modify
23 23 /// the contents of this method with the code editor.
24 24 /// </summary>
25 25 private void InitializeComponent() {
26 this.components = new System.ComponentModel.Container();
27 26 System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
28 System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
27 System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
29 28 System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
29 System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
30 30 this.eventsDataGrid = new System.Windows.Forms.DataGridView();
31 this.traceViewItemBindingSource = new System.Windows.Forms.BindingSource(this.components);
31 this.traceViewItemBindingSource = new System.Windows.Forms.BindingSource();
32 32 this.threadDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
33 this.TimeDelta = new System.Windows.Forms.DataGridViewTextBoxColumn();
33 34 this.Channel = new System.Windows.Forms.DataGridViewTextBoxColumn();
34 35 this.messageDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
35 36 ((System.ComponentModel.ISupportInitialize)(this.eventsDataGrid)).BeginInit();
36 37 ((System.ComponentModel.ISupportInitialize)(this.traceViewItemBindingSource)).BeginInit();
37 38 this.SuspendLayout();
38 39 //
39 40 // eventsDataGrid
40 41 //
41 42 this.eventsDataGrid.AllowUserToAddRows = false;
42 43 this.eventsDataGrid.AllowUserToDeleteRows = false;
43 44 this.eventsDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
44 45 | System.Windows.Forms.AnchorStyles.Left)
45 46 | System.Windows.Forms.AnchorStyles.Right)));
46 47 this.eventsDataGrid.AutoGenerateColumns = false;
47 48 this.eventsDataGrid.BackgroundColor = System.Drawing.SystemColors.Window;
48 49 dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
49 50 dataGridViewCellStyle1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
50 51 dataGridViewCellStyle1.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
51 52 dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText;
52 53 dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
53 54 dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
54 55 dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
55 56 this.eventsDataGrid.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
56 57 this.eventsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
57 58 this.eventsDataGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
58 59 this.threadDataGridViewTextBoxColumn,
60 this.TimeDelta,
59 61 this.Channel,
60 62 this.messageDataGridViewTextBoxColumn});
61 63 this.eventsDataGrid.DataSource = this.traceViewItemBindingSource;
62 dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
63 dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window;
64 dataGridViewCellStyle3.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
65 dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.ControlText;
66 dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight;
67 dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
68 dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
69 this.eventsDataGrid.DefaultCellStyle = dataGridViewCellStyle3;
64 dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
65 dataGridViewCellStyle4.BackColor = System.Drawing.SystemColors.Window;
66 dataGridViewCellStyle4.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
67 dataGridViewCellStyle4.ForeColor = System.Drawing.SystemColors.ControlText;
68 dataGridViewCellStyle4.SelectionBackColor = System.Drawing.SystemColors.Highlight;
69 dataGridViewCellStyle4.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
70 dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
71 this.eventsDataGrid.DefaultCellStyle = dataGridViewCellStyle4;
70 72 this.eventsDataGrid.Location = new System.Drawing.Point(12, 12);
71 73 this.eventsDataGrid.Name = "eventsDataGrid";
72 74 this.eventsDataGrid.ReadOnly = true;
75 this.eventsDataGrid.RowHeadersVisible = false;
73 76 this.eventsDataGrid.RowHeadersWidth = 17;
74 77 this.eventsDataGrid.RowTemplate.Resizable = System.Windows.Forms.DataGridViewTriState.False;
75 78 this.eventsDataGrid.Size = new System.Drawing.Size(939, 480);
76 79 this.eventsDataGrid.TabIndex = 1;
77 80 this.eventsDataGrid.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.eventsDataGrid_CellFormatting);
78 81 //
79 82 // traceViewItemBindingSource
80 83 //
81 84 this.traceViewItemBindingSource.DataSource = typeof(Implab.Diagnostics.Interactive.TraceViewItem);
82 85 //
83 86 // threadDataGridViewTextBoxColumn
84 87 //
85 this.threadDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCellsExceptHeader;
88 this.threadDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
86 89 this.threadDataGridViewTextBoxColumn.DataPropertyName = "Thread";
87 90 this.threadDataGridViewTextBoxColumn.HeaderText = "TID";
88 91 this.threadDataGridViewTextBoxColumn.Name = "threadDataGridViewTextBoxColumn";
89 92 this.threadDataGridViewTextBoxColumn.ReadOnly = true;
90 this.threadDataGridViewTextBoxColumn.Width = 5;
93 this.threadDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
94 this.threadDataGridViewTextBoxColumn.Width = 32;
95 //
96 // TimeDelta
97 //
98 this.TimeDelta.DataPropertyName = "TimeDelta";
99 dataGridViewCellStyle2.Format = "\'+\' 0 \'ms\'";
100 dataGridViewCellStyle2.NullValue = null;
101 this.TimeDelta.DefaultCellStyle = dataGridViewCellStyle2;
102 this.TimeDelta.HeaderText = "TimeDelta";
103 this.TimeDelta.Name = "TimeDelta";
104 this.TimeDelta.ReadOnly = true;
105 this.TimeDelta.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
91 106 //
92 107 // Channel
93 108 //
94 this.Channel.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
95 109 this.Channel.DataPropertyName = "Channel";
96 110 this.Channel.HeaderText = "Channel";
97 111 this.Channel.Name = "Channel";
98 112 this.Channel.ReadOnly = true;
99 this.Channel.Width = 79;
113 this.Channel.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
100 114 //
101 115 // messageDataGridViewTextBoxColumn
102 116 //
103 117 this.messageDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
104 118 this.messageDataGridViewTextBoxColumn.DataPropertyName = "FormattedMessage";
105 dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
106 this.messageDataGridViewTextBoxColumn.DefaultCellStyle = dataGridViewCellStyle2;
119 dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
120 this.messageDataGridViewTextBoxColumn.DefaultCellStyle = dataGridViewCellStyle3;
107 121 this.messageDataGridViewTextBoxColumn.HeaderText = "Message";
108 122 this.messageDataGridViewTextBoxColumn.Name = "messageDataGridViewTextBoxColumn";
109 123 this.messageDataGridViewTextBoxColumn.ReadOnly = true;
124 this.messageDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
110 125 //
111 126 // TraceForm
112 127 //
113 128 this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
114 129 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
115 130 this.ClientSize = new System.Drawing.Size(963, 504);
116 131 this.Controls.Add(this.eventsDataGrid);
117 132 this.Name = "TraceForm";
118 133 this.Text = "TraceForm";
119 134 ((System.ComponentModel.ISupportInitialize)(this.eventsDataGrid)).EndInit();
120 135 ((System.ComponentModel.ISupportInitialize)(this.traceViewItemBindingSource)).EndInit();
121 136 this.ResumeLayout(false);
122 137
123 138 }
124 139
125 140 #endregion
126 141
127 142 private System.Windows.Forms.DataGridView eventsDataGrid;
128 143 private System.Windows.Forms.BindingSource traceViewItemBindingSource;
129 144 private System.Windows.Forms.DataGridViewTextBoxColumn threadDataGridViewTextBoxColumn;
145 private System.Windows.Forms.DataGridViewTextBoxColumn TimeDelta;
130 146 private System.Windows.Forms.DataGridViewTextBoxColumn Channel;
131 147 private System.Windows.Forms.DataGridViewTextBoxColumn messageDataGridViewTextBoxColumn;
132
133 148 }
134 149 } No newline at end of file
@@ -1,126 +1,129
1 1 <?xml version="1.0" encoding="utf-8"?>
2 2 <root>
3 3 <!--
4 4 Microsoft ResX Schema
5 5
6 6 Version 2.0
7 7
8 8 The primary goals of this format is to allow a simple XML format
9 9 that is mostly human readable. The generation and parsing of the
10 10 various data types are done through the TypeConverter classes
11 11 associated with the data types.
12 12
13 13 Example:
14 14
15 15 ... ado.net/XML headers & schema ...
16 16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 17 <resheader name="version">2.0</resheader>
18 18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 24 </data>
25 25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 27 <comment>This is a comment</comment>
28 28 </data>
29 29
30 30 There are any number of "resheader" rows that contain simple
31 31 name/value pairs.
32 32
33 33 Each data row contains a name, and value. The row also contains a
34 34 type or mimetype. Type corresponds to a .NET class that support
35 35 text/value conversion through the TypeConverter architecture.
36 36 Classes that don't support this are serialized and stored with the
37 37 mimetype set.
38 38
39 39 The mimetype is used for serialized objects, and tells the
40 40 ResXResourceReader how to depersist the object. This is currently not
41 41 extensible. For a given mimetype the value must be set accordingly:
42 42
43 43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 44 that the ResXResourceWriter will generate, however the reader can
45 45 read any of the formats listed below.
46 46
47 47 mimetype: application/x-microsoft.net.object.binary.base64
48 48 value : The object must be serialized with
49 49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 50 : and then encoded with base64 encoding.
51 51
52 52 mimetype: application/x-microsoft.net.object.soap.base64
53 53 value : The object must be serialized with
54 54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 55 : and then encoded with base64 encoding.
56 56
57 57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 58 value : The object must be serialized into a byte array
59 59 : using a System.ComponentModel.TypeConverter
60 60 : and then encoded with base64 encoding.
61 61 -->
62 62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 64 <xsd:element name="root" msdata:IsDataSet="true">
65 65 <xsd:complexType>
66 66 <xsd:choice maxOccurs="unbounded">
67 67 <xsd:element name="metadata">
68 68 <xsd:complexType>
69 69 <xsd:sequence>
70 70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 71 </xsd:sequence>
72 72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 73 <xsd:attribute name="type" type="xsd:string" />
74 74 <xsd:attribute name="mimetype" type="xsd:string" />
75 75 <xsd:attribute ref="xml:space" />
76 76 </xsd:complexType>
77 77 </xsd:element>
78 78 <xsd:element name="assembly">
79 79 <xsd:complexType>
80 80 <xsd:attribute name="alias" type="xsd:string" />
81 81 <xsd:attribute name="name" type="xsd:string" />
82 82 </xsd:complexType>
83 83 </xsd:element>
84 84 <xsd:element name="data">
85 85 <xsd:complexType>
86 86 <xsd:sequence>
87 87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 89 </xsd:sequence>
90 90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 93 <xsd:attribute ref="xml:space" />
94 94 </xsd:complexType>
95 95 </xsd:element>
96 96 <xsd:element name="resheader">
97 97 <xsd:complexType>
98 98 <xsd:sequence>
99 99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 100 </xsd:sequence>
101 101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 102 </xsd:complexType>
103 103 </xsd:element>
104 104 </xsd:choice>
105 105 </xsd:complexType>
106 106 </xsd:element>
107 107 </xsd:schema>
108 108 <resheader name="resmimetype">
109 109 <value>text/microsoft-resx</value>
110 110 </resheader>
111 111 <resheader name="version">
112 112 <value>2.0</value>
113 113 </resheader>
114 114 <resheader name="reader">
115 115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 116 </resheader>
117 117 <resheader name="writer">
118 118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 119 </resheader>
120 <metadata name="TimeDelta.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
121 <value>True</value>
122 </metadata>
120 123 <metadata name="Channel.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
121 124 <value>True</value>
122 125 </metadata>
123 126 <metadata name="traceViewItemBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
124 127 <value>17, 17</value>
125 128 </metadata>
126 129 </root> No newline at end of file
@@ -1,26 +1,27
1 1 using System;
2 2 using System.Collections.Generic;
3 3 using System.Linq;
4 4 using System.Text;
5 5 using System.Threading.Tasks;
6 6
7 7 namespace Implab.Diagnostics.Interactive {
8 8 public class TraceViewItem {
9 9 string m_formattedValue;
10 10
11 11 public string Message { get; set; }
12 public int TimeDelta { get; set; }
12 13 public int Timestamp { get; set; }
13 14 public int Indent { get; set; }
14 15 public int Thread { get; set; }
15 16 public string Channel { get; set; }
16 17
17 18 public string FormattedMessage {
18 19 get {
19 20 if (m_formattedValue == null) {
20 21 m_formattedValue = Message.Replace("\r",String.Empty).Replace("\n", " | ");
21 22 }
22 23 return m_formattedValue;
23 24 }
24 25 }
25 26 }
26 27 }
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved
author

ok, latest stable version should be in default

You need to be logged in to leave comments. Login now