aboutsummaryrefslogtreecommitdiffstatsci
path: root/Samples/DebugLogAnalyzer/src
diff options
context:
space:
mode:
Diffstat (limited to 'Samples/DebugLogAnalyzer/src')
-rw-r--r--Samples/DebugLogAnalyzer/src/DbgEventWidget.cs142
-rw-r--r--Samples/DebugLogAnalyzer/src/DbgLogViewer.cs805
-rw-r--r--Samples/DebugLogAnalyzer/src/DebugInterface.cs114
-rw-r--r--Samples/DebugLogAnalyzer/src/DebugInterfaceWidget.cs281
-rw-r--r--Samples/DebugLogAnalyzer/src/Editor.cs44
-rw-r--r--Samples/DebugLogAnalyzer/src/Extensions.cs23
-rw-r--r--Samples/DebugLogAnalyzer/src/Program.cs466
7 files changed, 1875 insertions, 0 deletions
diff --git a/Samples/DebugLogAnalyzer/src/DbgEventWidget.cs b/Samples/DebugLogAnalyzer/src/DbgEventWidget.cs
new file mode 100644
index 00000000..19666ee2
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/DbgEventWidget.cs
@@ -0,0 +1,142 @@
+// Copyright (c) 2013-2020 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Crow.Cairo;
+using Crow.DebugLogger;
+using DebugLogAnalyzer;
+
+namespace Crow
+{
+ public class DbgEventWidget : Widget
+ {
+ public DbgEventWidget (){}
+
+ DbgEvent evt, hoverEvt;
+ long ticksPerPixel;
+ double pixelPerTick;
+
+ public DbgEvent Event {
+ get => evt;
+ set {
+ if (evt == value)
+ return;
+ evt = value;
+ updatePixelPerTicks ();
+ NotifyValueChangedAuto (evt);
+ RegisterForRedraw ();
+ }
+ }
+ public DbgEvent HoverEvent {
+ get => hoverEvt;
+ private set {
+ if (hoverEvt == value)
+ return;
+ hoverEvt = value;
+ NotifyValueChangedAuto (hoverEvt);
+ }
+ }
+
+ [DefaultValue ("1000")]
+ public long TicksPerPixel {
+ get => ticksPerPixel;
+ set {
+ if (ticksPerPixel == value)
+ return;
+ ticksPerPixel = value;
+ NotifyValueChangedAuto (ticksPerPixel);
+ if (Width == Measure.Fit)
+ RegisterForLayouting (LayoutingType.Width);
+ }
+ }
+
+ public override int measureRawSize (LayoutingType lt)
+ {
+ updatePixelPerTicks ();
+ if (lt == LayoutingType.Width)
+ contentSize.Width = Event == null ? 0 : (int)Math.Max(pixelPerTick * Event.Duration, 2);
+
+ return base.measureRawSize (lt);
+ }
+
+ public override void OnLayoutChanges (LayoutingType layoutType)
+ {
+ if (layoutType == LayoutingType.Width)
+ updatePixelPerTicks ();
+
+ base.OnLayoutChanges (layoutType);
+ }
+
+ protected override void onDraw (Context gr)
+ {
+ if (Event == null) {
+ base.onDraw (gr);
+ return;
+ }
+
+ gr.LineWidth = 1;
+ gr.SetDash (new double [] { 1.0, 3.0 }, 0);
+
+ Rectangle cb = ClientRectangle;
+
+ if (Event.Duration == 0) {
+ gr.SetSource (Event.Color);
+ gr.Rectangle (cb);
+ gr.Fill ();
+ return;
+ }
+
+ drawEvent (gr, cb.Height, Event);
+ }
+ void drawEvent (Context ctx, int h, DbgEvent dbge)
+ {
+ double w = Math.Max(dbge.Duration * pixelPerTick, 2.0);
+ double x = (dbge.begin - Event.begin) * pixelPerTick;
+
+ ctx.Rectangle (x, 0, w, h);
+ ctx.SetSource (dbge.Color);
+ /*if (dbge.IsSelected) {
+ ctx.FillPreserve ();
+ ctx.SetSource (1, 1, 1);
+ ctx.Stroke ();
+ }else*/
+ ctx.Fill ();
+
+ if (dbge.Events == null)
+ return;
+ foreach (DbgEvent e in dbge.Events)
+ drawEvent (ctx, h, e);
+ }
+
+ public override void onMouseMove (object sender, MouseMoveEventArgs e)
+ {
+ if (Event != null) {
+ Point m = ScreenPointToLocal (e.Position);
+ long curTick = (long)(m.X / pixelPerTick) + Event.begin;
+ HoverEvent = hoverEvent (Event, curTick);
+
+ e.Handled = true;
+ }
+ base.onMouseMove (sender, e);
+ }
+
+ DbgEvent hoverEvent (DbgEvent hevt, long curTick){
+ if (hevt.Events != null) {
+ foreach (DbgEvent e in hevt.Events) {
+ if (curTick >= e.begin && curTick <= e.end)
+ return hoverEvent (e, curTick);
+ }
+ }
+ return hevt;
+ }
+ void updatePixelPerTicks ()
+ {
+ if (Width == Measure.Fit)
+ pixelPerTick = 1.0 / ticksPerPixel;
+ else
+ pixelPerTick = Event == null ? 0 : (double)ClientRectangle.Width / Event.Duration;
+ }
+ }
+} \ No newline at end of file
diff --git a/Samples/DebugLogAnalyzer/src/DbgLogViewer.cs b/Samples/DebugLogAnalyzer/src/DbgLogViewer.cs
new file mode 100644
index 00000000..1273c9e6
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/DbgLogViewer.cs
@@ -0,0 +1,805 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices.ComTypes;
+// Copyright (c) 2013-2020 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Crow.Cairo;
+using Crow.DebugLogger;
+using DebugLogAnalyzer;
+
+namespace Crow
+{
+ public class DbgLogViewer : Widget
+ {
+ public static Dictionary<DbgEvtType, Color> colors;
+
+ public static Configuration colorsConf = new Configuration ("dbgcolor.conf");//, Interface.GetStreamFromPath("#Crow.dbgcolor.conf"));
+
+ public static void reloadColors () {
+ colors = new Dictionary<DbgEvtType, Color>();
+ foreach (string n in colorsConf.Names) {
+ DbgEvtType t = (DbgEvtType)Enum.Parse (typeof(DbgEvtType), n);
+ Color c = colorsConf.Get<Color> (n);
+ colors.Add (t, c);
+ }
+
+ }
+ #region CTOR
+ static DbgLogViewer() {
+ //reloadColors ();
+
+ }
+ protected DbgLogViewer () : base(){}
+ public DbgLogViewer (Interface iface, string style = null) : base(iface, style){}
+ #endregion
+
+ FontExtents fe;
+
+
+ double xScale = 1.0/1024.0, yScale = 1.0, leftMargin, topMargin = 0.0;
+ DbgWidgetRecord curWidget, hoverWidget;
+ DbgEvent curEvent, hoverEvent;
+
+ List<DbgEvent> events = new List<DbgEvent> ();
+ List<DbgWidgetRecord> widgets = new List<DbgWidgetRecord> ();
+
+
+ public DbgEvtType Filter {
+ get => Configuration.Global.Get<DbgEvtType> ("DbgLogViewFilter");
+ set {
+ if (Filter == value)
+ return;
+ Configuration.Global.Set ("DbgLogViewFilter", value);
+ NotifyValueChangedAuto(Filter);
+ RegisterForGraphicUpdate();
+ }
+ }
+ public List<DbgEvent> Events {
+ get => events;
+ set {
+ if (events == value)
+ return;
+ events = value;
+ NotifyValueChanged (nameof (Events), events);
+
+ maxTicks = minTicks = 0;
+ if (events != null && events.Count > 0) {
+ minTicks = long.MaxValue;
+ foreach (DbgEvent e in events) {
+ if (e.begin < minTicks)
+ minTicks = e.begin;
+ if (e.end > maxTicks)
+ maxTicks = e.end;
+ }
+ visibleTicks = maxTicks - minTicks;
+ XScale = (ClientRectangle.Width - leftMargin)/visibleTicks;
+ ScrollX = 0;
+ ScrollY = 0;
+ } else {
+ maxTicks = 1;
+ XScale = 1.0/1024.0;
+ }
+
+
+ RegisterForGraphicUpdate ();
+ }
+ }
+ public List<DbgWidgetRecord> Widgets {
+ get => widgets;
+ set {
+ if (widgets == value)
+ return;
+ widgets = value;
+ NotifyValueChanged (nameof (Widgets), widgets);
+ updateMargins ();
+ updateMaxScrollX ();
+ updateMaxScrollY ();
+ }
+ }
+ public DbgWidgetRecord CurrentWidget {
+ get => curWidget;
+ set {
+ if (curWidget == value)
+ return;
+ curWidget = value;
+ NotifyValueChanged (nameof (CurrentWidget), curWidget);
+ if (CurrentWidget == null)
+ return;
+ if (CurrentWidget.listIndex < scrollY || CurrentWidget.listIndex > scrollY + visibleLines)
+ ScrollY = CurrentWidget.listIndex - (visibleLines / 2);
+
+ currentLine = CurrentWidget.listIndex;
+ RegisterForRedraw();
+ }
+ }
+ public DbgEvent CurrentEvent {
+ get => curEvent;
+ set {
+ if (curEvent == value)
+ return;
+ /*if (curEvent != null)
+ curEvent.IsSelected = false;*/
+ curEvent = value;
+ if (curEvent != null) {
+ //curEvent.IsSelected = true;
+ if (curEvent is DbgWidgetEvent we) {
+ //CurrentWidget = Widgets [we.InstanceIndex];
+ hoverLine = we.InstanceIndex;
+ }
+ currentTick = curEvent.begin;
+ if (curEvent.begin > minTicks + ScrollX + visibleTicks ||
+ curEvent.end < minTicks + ScrollX)
+ ScrollX = curEvent.begin - minTicks - visibleTicks / 2;
+ }
+ NotifyValueChanged (nameof (CurrentEvent), curEvent);
+ RegisterForRedraw ();
+ }
+ }
+ public DbgWidgetRecord HoverWidget {
+ get => hoverWidget;
+ internal set {
+ if (hoverWidget == value)
+ return;
+ hoverWidget = value;
+ NotifyValueChanged (nameof (HoverWidget), hoverWidget);
+ }
+ }
+
+ public DbgEvent HoverEvent {
+ get => hoverEvent;
+ set {
+ if (hoverEvent == value)
+ return;
+ hoverEvent = value;
+ NotifyValueChanged (nameof (HoverEvent), hoverEvent);
+ }
+ }
+
+ long hoverTick = 0, currentTick, selStart = -1, selEnd = -1, minTicks = 0, maxTicks = 0, visibleTicks = 0;
+ int hoverLine = -1, currentLine = -1;
+ int visibleLines = 1;
+ Point mousePos;
+
+ public double XScale {
+ get => xScale;
+ set {
+ if (xScale == value)
+ return;
+ xScale = value;
+ NotifyValueChanged ("XScale", xScale);
+ updateVisibleTicks ();
+ RegisterForGraphicUpdate ();
+ }
+ }
+ public double YScale {
+ get => yScale;
+ set {
+ if (yScale == value)
+ return;
+ yScale = value;
+ NotifyValueChanged ("YScale", yScale);
+ RegisterForGraphicUpdate ();
+ }
+ }
+ public override Font Font {
+ get { return base.Font; }
+ set {
+ base.Font = value;
+ using (Context gr = new Context (IFace.surf)) {
+ gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight);
+ gr.SetFontSize (Font.Size);
+
+ fe = gr.FontExtents;
+ }
+ updateMargins ();
+ }
+ }
+
+ void drawEvents (Context ctx, List<DbgEvent> evts)
+ {
+ if (evts == null || evts.Count == 0)
+ return;
+ Rectangle cb = ClientRectangle;
+
+ foreach (DbgEvent evt in evts) {
+ if ((evt.Category & currentFilter) == currentFilter) {
+ if (evt.end - minTicks <= ScrollX)
+ continue;
+ if (evt.begin - minTicks > ScrollX + visibleTicks)
+ break;
+ double penY = topMargin + ClientRectangle.Top;
+
+ if (evt.type.HasFlag (DbgEvtType.Widget)) {
+ DbgWidgetEvent eW = evt as DbgWidgetEvent;
+ int lIdx = eW.InstanceIndex - ScrollY;
+ if (lIdx >= 0 && lIdx <= visibleLines) {
+
+ penY += (lIdx) * fe.Height;
+
+ ctx.SetSource (evt.Color);
+
+ double x = xScale * (evt.begin - minTicks - ScrollX);
+ double w = Math.Max (Math.Max (2.0, 2.0 * xScale), (double)(evt.end - evt.begin) * xScale);
+ if (x < 0.0) {
+ w += x;
+ x = 0.0;
+ }
+ x += leftMargin + cb.Left;
+ double rightDiff = x + w - cb.Right;
+ if (rightDiff > 0)
+ w -= rightDiff;
+ RectangleD r = new RectangleD(x, penY, w, fe.Height);
+ ctx.Rectangle (r);
+ ctx.Fill ();
+ /*if (evt == CurrentEvent) {
+ r.Inflate(2,2);
+ ctx.SetSource(Colors.White);
+ ctx.Rectangle(r);
+ ctx.Stroke();
+ }*/
+ }
+ } else if (evt.type.HasFlag (DbgEvtType.IFace)) {
+ double x = xScale * (evt.begin - minTicks - ScrollX);
+ double w = Math.Max (Math.Max (2.0, 2.0 * xScale), (double)(evt.end - evt.begin) * xScale);
+ if (x < 0.0) {
+ w += x;
+ x = 0.0;
+ }
+ x += leftMargin + cb.Left;
+ double rightDiff = x + w - cb.Right;
+ if (rightDiff > 0)
+ w -= rightDiff;
+ //ctx.SetSource (0.9,0.9,0.0,0.1);
+ ctx.SetSource (evt.Color.AdjustAlpha(0.15));
+ ctx.Rectangle (x, cb.Top + topMargin, w, cb.Height);
+ ctx.Fill ();
+ }
+ }
+ drawEvents (ctx, evt.Events);
+ }
+ }
+
+ DbgEvtType currentFilter;
+ protected override void onDraw (Cairo.Context gr)
+ {
+ base.onDraw (gr);
+
+ gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight);
+ gr.SetFontSize (Font.Size);
+ gr.FontOptions = Interface.FontRenderingOptions;
+ gr.Antialias = Cairo.Antialias.None;
+
+ if (widgets == null)
+ return;
+
+ gr.LineWidth = 1.0;
+
+ Rectangle cb = ClientRectangle;
+
+ double penY = topMargin + ClientRectangle.Top;
+
+ for (int i = 0; i < visibleLines; i++) {
+ if (i + ScrollY >= widgets.Count)
+ break;
+ int gIdx = i + ScrollY;
+ DbgWidgetRecord g = widgets [gIdx];
+
+ penY += fe.Height;
+
+ gr.SetSource (Crow.Colors.Jet);
+ gr.MoveTo (cb.X, penY - 0.5);
+ gr.LineTo (cb.Right, penY - 0.5);
+ gr.Stroke ();
+
+ double penX = 5.0 * g.xLevel + cb.Left;
+
+ if (g.yIndex == 0)
+ gr.SetSource (Crow.Colors.LightSalmon);
+ else if (currentLine == g.listIndex)
+ gr.SetSource(Colors.RoyalBlue);
+ else
+ Foreground.SetAsSource (IFace, gr);
+
+ gr.MoveTo (penX, penY - gr.FontExtents.Descent);
+ gr.ShowText (g.name + gIdx);
+ }
+
+ currentFilter = Filter;
+ drawEvents (gr, events);
+
+ gr.MoveTo (cb.Left, topMargin - 0.5 + cb.Top);
+ gr.LineTo (cb.Right, topMargin - 0.5 + cb.Top);
+
+ gr.MoveTo (leftMargin + cb.Left, cb.Top);
+ gr.LineTo (leftMargin + cb.Left, cb.Bottom);
+ gr.SetSource (Crow.Colors.Grey);
+
+ penY = topMargin + ClientRectangle.Top;
+
+ //graduation
+ long largeGrad = long.Parse ("1" + new string ('0', visibleTicks.ToString ().Length - 1));
+ long smallGrad = Math.Max (1, largeGrad / 10);
+
+ long firstVisibleTicks = minTicks + ScrollX;
+ long curGrad = firstVisibleTicks - firstVisibleTicks % smallGrad + smallGrad;
+
+ long gg = curGrad - ScrollX - minTicks;
+ while (gg < visibleTicks ) {
+ double x = (double)gg * xScale + leftMargin + cb.Left;
+
+ gr.MoveTo (x, penY - 0.5);
+ if (curGrad % largeGrad == 0) {
+ gr.LineTo (x, penY - 8.5);
+ string str = ticksToMS(curGrad);
+ TextExtents te = gr.TextExtents (str);
+ gr.RelMoveTo (-0.5 * te.Width, -2.0);
+ gr.ShowText (str);
+ }else
+ gr.LineTo (x, penY - 2.5);
+
+ curGrad += smallGrad;
+ gg = curGrad - ScrollX - minTicks;
+ }
+
+ gr.Stroke ();
+
+
+
+ }
+ string ticksToMS(long ticks) => Math.Round ((double)ticks / Stopwatch.Frequency * 1000.0, 2).ToString();
+ public override void Paint (Cairo.Context ctx)
+ {
+ base.Paint (ctx);
+
+ Rectangle r = new Rectangle(mousePos.X, 0, 1, Slot.Height);
+ Rectangle ctxR = ContextCoordinates (r);
+ Rectangle cb = ClientRectangle;
+ ctx.LineWidth = 1.0;
+ if (hoverTick >= 0) {
+ double x = xScale * (hoverTick - minTicks - ScrollX) + leftMargin;
+ if (x - Math.Truncate (x) > 0.5)
+ x = Math.Truncate (x) + 0.5;
+ else
+ x = Math.Truncate (x) - 0.5;
+ ctx.MoveTo (x, cb.Top + topMargin - 4.0);
+ ctx.LineTo (x, cb.Bottom);
+ ctx.SetSource (0.7,0.7,0.7,0.5);
+ ctx.Stroke();
+ }
+ if (currentTick >= 0) {
+ double x = xScale * (currentTick - minTicks - ScrollX) + leftMargin;
+ if (x > leftMargin && x < cb.Right) {
+ if (x - Math.Truncate (x) > 0.5)
+ x = Math.Truncate (x) + 0.5;
+ else
+ x = Math.Truncate (x) - 0.5;
+ ctx.MoveTo (x, cb.Top);
+ ctx.LineTo (x, cb.Bottom);
+ ctx.SetSource (0.2,0.7,1.0,0.6);
+ ctx.Stroke();
+ }
+ }
+
+ ctx.SelectFontFace (Font.Name, Font.Slant, Font.Wheight);
+ ctx.SetFontSize (Font.Size);
+ ctx.FontOptions = Interface.FontRenderingOptions;
+ ctx.Antialias = Interface.Antialias;
+
+ string str = ticksToMS(hoverTick);
+
+ ctx.MoveTo (ctxR.X - ctx.TextExtents (str).Width / 2, ctxR.Y + fe.Height);
+ ctx.ShowText (str);
+
+ ctx.Operator = Cairo.Operator.Add;
+
+ if (hoverLine >= 0) {
+ double y = fe.Height * (hoverLine - ScrollY) + topMargin + cb.Top;
+ r = new Rectangle (cb.Left, (int)y, cb.Width, (int)fe.Height);
+
+ ctx.SetSource (0.1, 0.1, 0.1, 0.4);
+ ctx.Rectangle (ContextCoordinates (r));
+ ctx.Fill ();
+ }
+
+ if (currentLine >= ScrollY && currentLine < scrollY + visibleLines) {
+ double y = fe.Height * (currentLine - ScrollY) + topMargin + cb.Top;
+ r = new Rectangle (cb.Left, (int)y, cb.Width, (int)fe.Height);
+
+ ctx.SetSource (0.1, 0.1, 0.7, 0.2);
+ ctx.Rectangle (ContextCoordinates (r));
+ ctx.Fill ();
+ }
+
+ if (selStart < 0 || selEnd < 0) {
+ ctx.Operator = Cairo.Operator.Over;
+ return;
+ }
+ double selStartX = (double)(selStart - ScrollX - minTicks) * xScale + leftMargin + cb.Left;
+ double selEndX = (double)(selEnd - ScrollX - minTicks) * xScale + leftMargin + cb.Left;
+
+ if (selStartX < selEndX) {
+ ctxR.X = (int)selStartX;
+ ctxR.Width = (int)(selEndX - selStartX);
+ } else {
+ ctxR.X = (int)selEndX;
+ ctxR.Width = (int)(selStartX - selEndX);
+ }
+
+ ctxR.Width = Math.Max (1, ctxR.Width);
+ ctx.Rectangle (ctxR);
+ //ctx.SetSourceColor (Color.LightYellow);
+ ctx.SetSource (Colors.Jet);
+ ctx.Fill();
+ ctx.Operator = Cairo.Operator.Over;
+
+ }
+ public override void OnLayoutChanges (LayoutingType layoutType)
+ {
+ base.OnLayoutChanges (layoutType);
+ switch (layoutType) {
+ case LayoutingType.Width:
+ if (xScale < 0) {
+ visibleTicks = maxTicks - minTicks;
+ XScale = (ClientRectangle.Width - leftMargin) / visibleTicks;
+ }
+ updateVisibleTicks ();
+ break;
+ case LayoutingType.Height:
+ updateVisibleLines ();
+ break;
+ }
+ }
+
+ public override void onMouseLeave (object sender, MouseMoveEventArgs e)
+ {
+ base.onMouseLeave (sender, e);
+ hoverLine = -1;
+ hoverTick = 0;
+ }
+ public override void onMouseMove (object sender, MouseMoveEventArgs e)
+ {
+ long lastTick = hoverTick;
+ int lastLine = hoverLine;
+ updateMouseLocalPos (e.Position);
+
+ if (IFace.IsDown (Glfw.MouseButton.Left) && selStart >= 0)
+ selEnd = hoverTick;
+ else if (IFace.IsDown(Glfw.MouseButton.Right)) {
+ if (lastTick >= 0 && hoverTick >= 0)
+ ScrollX += lastTick - hoverTick;
+ if (lastLine >= 0 && hoverLine >= 0)
+ ScrollY += lastLine - hoverLine;
+ updateMouseLocalPos (e.Position);
+ } else {
+ HoverWidget = (hoverLine < 0 || hoverLine >= widgets.Count) ? null : widgets [hoverLine];
+ HoverEvent = hoverWidget?.Events.FirstOrDefault (ev => ev.begin <= hoverTick && ev.end >= hoverTick);
+ Task.Run (() => findHoverEvent (hoverWidget, hoverTick));
+ }
+
+ RegisterForRepaint();
+
+ e.Handled = true;
+ base.onMouseMove (sender, e);
+ }
+ void findHoverEvent (DbgWidgetRecord widget, long tick) {
+ DbgEvent tmp = widget?.Events.FirstOrDefault (ev => ev.begin <= tick && ev.end >= tick);
+ if (tmp == null) {
+ tmp = Events.Where(e=>e.type.HasFlag(DbgEvtType.IFace)).Where (ev => ev.begin <= tick && ev.end >= tick).FirstOrDefault();
+ while(tmp != null) {
+ DbgEvent che = tmp.Events?.Where(e=>e.type.HasFlag(DbgEvtType.IFace)).Where (ev => ev.begin <= tick && ev.end >= tick).FirstOrDefault();
+ if (che == null)
+ break;
+ tmp = che;
+ }
+ } else {
+ while(tmp != null) {
+ DbgEvent che = tmp.Events?.OfType<DbgWidgetEvent>()?.Where(ev=>ev.InstanceIndex == widget.listIndex && ev.begin <= tick && ev.end >= tick).FirstOrDefault();
+ if (che == null)
+ break;
+ tmp = che;
+ }
+ }
+ HoverEvent = tmp;
+ }
+ public override void onMouseClick(object sender, MouseButtonEventArgs e)
+ {
+ if (e.Button == Glfw.MouseButton.Left && selEnd < 0) {
+ currentTick = hoverTick;
+ currentLine = hoverLine;
+ CurrentWidget = hoverWidget;
+ CurrentEvent = hoverEvent;
+ }
+ selStart = -1;
+ selEnd = -1;
+
+ e.Handled = true;
+ base.onMouseClick(sender, e);
+ }
+ public override void onMouseDown (object sender, MouseButtonEventArgs e)
+ {
+ if (e.Button == Glfw.MouseButton.Left) {
+ selStart = hoverTick;
+ selEnd = -1;
+ }
+
+ RegisterForRedraw ();
+ e.Handled = true;
+ base.onMouseDown (sender, e);
+ }
+ public override void onMouseUp (object sender, MouseButtonEventArgs e)
+ {
+
+ if (e.Button == Glfw.MouseButton.Left && selEnd > 0 && selEnd != selStart) {
+ long scrX = 0;
+ if (selStart < selEnd) {
+ visibleTicks = selEnd - selStart;
+ scrX = selStart - minTicks;
+ } else {
+ visibleTicks = selStart - selEnd;
+ scrX = selEnd - minTicks;
+ }
+ XScale = (ClientRectangle.Width - leftMargin) / visibleTicks;
+ ScrollX = scrX;
+ }
+
+ RegisterForRedraw ();
+ e.Handled = true;
+ base.onMouseUp (sender, e);
+ }
+
+ /// <summary> Process scrolling vertically, or if shift is down, vertically </summary>
+ public override void onMouseWheel (object sender, MouseWheelEventArgs e)
+ {
+ //base.onMouseWheel (sender, e);
+
+ if (IFace.Shift)
+ ScrollX -= (int)((double)(e.Delta * MouseWheelSpeed) / xScale);
+ else if (IFace.Ctrl) {
+ if (e.Delta > 0) {
+ XScale *= 2.0;
+ } else {
+ if (MaxScrollX > 0)
+ XScale *= 0.5;
+ }
+ ScrollX = (long)(hoverTick - (long)((double)Math.Max(0, mousePos.X - (long)leftMargin) / xScale) - minTicks);
+ }else
+ ScrollY -= e.Delta * MouseWheelSpeed;
+ }
+
+ public override void onKeyDown (object sender, KeyEventArgs e)
+ {
+ base.onKeyDown (sender, e);
+
+ if (e.Key == Glfw.Key.F3) {
+ if (selEnd < 0)
+ return;
+ if (selEnd < selStart)
+ zoom (selEnd, selStart);
+ else
+ zoom (selStart, selEnd);
+ selEnd = selStart = -1;
+ }
+ }
+
+ void updateMargins ()
+ {
+ leftMargin = topMargin = 0.0;
+
+ if (widgets == null)
+ return;
+
+ using (Context gr = new Context (IFace.surf)) {
+ double maxNameWidth = 0.0;
+
+ gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight);
+ gr.SetFontSize (Font.Size);
+
+ foreach (DbgWidgetRecord o in widgets) {
+ double nameWidth = gr.TextExtents (o.name).Width + 5.0 * o.xLevel;
+ if (nameWidth > maxNameWidth)
+ maxNameWidth = nameWidth;
+ }
+
+ leftMargin = 10.5 + maxNameWidth;
+ topMargin = 2.0 * fe.Height;
+
+ RegisterForGraphicUpdate ();
+ }
+ }
+
+ void updateVisibleLines ()
+ {
+ visibleLines = fe.Height < 1 ? 1 : (int)Math.Ceiling (((double)ClientRectangle.Height - topMargin) / fe.Height);
+ NotifyValueChanged ("VisibleLines", visibleLines);
+ updateMaxScrollY ();
+ }
+ void updateVisibleTicks ()
+ {
+ visibleTicks = Math.Max (0, (long)((double)(ClientRectangle.Width - leftMargin) / XScale));
+ NotifyValueChanged ("VisibleTicks", visibleTicks);
+ updateMaxScrollX ();
+ }
+
+ void updateMaxScrollX ()
+ {
+ if (widgets == null) {
+ MaxScrollX = 0;
+ } else {
+ long tot = maxTicks - minTicks;
+ MaxScrollX = Math.Max (0L, tot - visibleTicks);
+ NotifyValueChanged ("ChildWidthRatio", (double)visibleTicks / tot);
+ }
+ }
+ void updateMaxScrollY ()
+ {
+ if (widgets == null)
+ MaxScrollY = 0;
+ else {
+ MaxScrollY = Math.Max (0, widgets.Count + 1 - visibleLines);
+ NotifyValueChanged ("ChildHeightRatio", (double)visibleLines / (widgets.Count + 1));
+ }
+ }
+
+ void updateMouseLocalPos (Point mPos)
+ {
+ Rectangle r = ScreenCoordinates (Slot);
+ Rectangle cb = ClientRectangle;
+ cb.Left += (int)leftMargin;
+ cb.Width -= (int)leftMargin;
+ cb.Y += (int)topMargin;
+ cb.Height -= (int)topMargin;
+
+ mousePos = mPos - r.Position;
+
+ mousePos.X = Math.Max (cb.X, mousePos.X);
+ mousePos.X = Math.Min (cb.Right, mousePos.X);
+
+ if (mousePos.Y < cb.Top || mousePos.Y > cb.Bottom)
+ hoverLine = -1;
+ else
+ hoverLine = (int)((double)(mousePos.Y - cb.Top) / fe.Height) + ScrollY;
+
+ NotifyValueChanged ("CurrentLine", hoverLine);
+
+ mousePos.Y = Math.Max (cb.Y, mousePos.Y);
+ mousePos.Y = Math.Min (cb.Bottom, mousePos.Y);
+
+ hoverTick = (long)((double)(mousePos.X - cb.X) / xScale) + minTicks + ScrollX;
+ RegisterForRedraw ();
+ }
+ void zoom (long start, long end) {
+ //Rectangle cb = ClientRectangle;
+ //cb.X += (int)leftMargin;
+ XScale = ((double)ClientRectangle.Width - leftMargin)/(end - start);
+ ScrollX = (int)(start - minTicks);
+ }
+
+
+ long scrollX, maxScrollX;
+ int scrollY, maxScrollY, mouseWheelSpeed;
+
+ /// <summary>
+ /// if true, key stroke are handled in derrived class
+ /// </summary>
+ protected bool KeyEventsOverrides = false;
+
+ /// <summary> Horizontal Scrolling Position </summary>
+ [DefaultValue(0)]
+ public virtual long ScrollX {
+ get => scrollX;
+ set {
+ if (scrollX == value)
+ return;
+
+ long newS = value;
+ if (newS < 0)
+ newS = 0;
+ else if (newS > maxScrollX)
+ newS = maxScrollX;
+
+ if (newS == scrollX)
+ return;
+
+ scrollX = newS;
+
+ NotifyValueChangedAuto (scrollX);
+ RegisterForGraphicUpdate ();
+ }
+ }
+ /// <summary> Vertical Scrolling Position </summary>
+ [DefaultValue(0)]
+ public virtual int ScrollY {
+ get => scrollY;
+ set {
+ if (scrollY == value)
+ return;
+
+ int newS = value;
+ if (newS < 0)
+ newS = 0;
+ else if (newS > maxScrollY)
+ newS = maxScrollY;
+
+ if (newS == scrollY)
+ return;
+
+ scrollY = newS;
+
+ NotifyValueChangedAuto (scrollY);
+ RegisterForGraphicUpdate ();
+
+ if (widgets == null)
+ return;
+
+ Rectangle cb = ClientRectangle;
+ cb.Left += (int)leftMargin;
+ cb.Width -= (int)leftMargin;
+ cb.Y += (int)topMargin;
+ cb.Height -= (int)topMargin;
+
+ if (mousePos.Y < cb.Top || mousePos.Y > cb.Bottom)
+ hoverLine = -1;
+ else
+ hoverLine = (int)((double)(mousePos.Y - cb.Top) / fe.Height) + ScrollY;
+
+ NotifyValueChanged ("CurrentLine", hoverLine);
+ }
+ }
+ /// <summary> Horizontal Scrolling maximum value </summary>
+ [DefaultValue(0)]
+ public virtual long MaxScrollX {
+ get => maxScrollX;
+ set {
+ if (maxScrollX == value)
+ return;
+
+ maxScrollX = Math.Max(0, value);
+
+ if (scrollX > maxScrollX)
+ ScrollX = maxScrollX;
+
+ NotifyValueChangedAuto (maxScrollX);
+ RegisterForGraphicUpdate ();
+ }
+ }
+ /// <summary> Vertical Scrolling maximum value </summary>
+ [DefaultValue(0)]
+ public virtual int MaxScrollY {
+ get => maxScrollY;
+ set {
+ if (maxScrollY == value)
+ return;
+
+ maxScrollY = Math.Max (0, value);
+
+ if (scrollY > maxScrollY)
+ ScrollY = maxScrollY;
+
+ NotifyValueChangedAuto (maxScrollY);
+ RegisterForGraphicUpdate ();
+ }
+ }
+ /// <summary> Mouse Wheel Scrolling multiplier </summary>
+ [DefaultValue(1)]
+ public virtual int MouseWheelSpeed {
+ get => mouseWheelSpeed;
+ set {
+ if (mouseWheelSpeed == value)
+ return;
+
+ mouseWheelSpeed = value;
+
+ NotifyValueChangedAuto (mouseWheelSpeed);
+ }
+ }
+ }
+}
+
+
diff --git a/Samples/DebugLogAnalyzer/src/DebugInterface.cs b/Samples/DebugLogAnalyzer/src/DebugInterface.cs
new file mode 100644
index 00000000..c0615321
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/DebugInterface.cs
@@ -0,0 +1,114 @@
+// Copyright (c) 2013-2019 Bruyère Jean-Philippe <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Crow.Cairo;
+using System.Threading;
+
+namespace Crow
+{
+ public class DebugInterface : SampleBase {
+ static DebugInterface() {
+ DbgLogger.IncludeEvents = DbgEvtType.None;
+ DbgLogger.DiscardEvents = DbgEvtType.None;
+ DbgLogger.ConsoleOutput = true;
+ }
+ public DebugInterface (IntPtr hWin) : base (hWin)
+ {
+ surf = new ImageSurface (Format.Argb32, 100, 100);
+ }
+
+ public override void Run()
+ {
+ Init();
+
+ Thread t = new Thread (interfaceThread) {
+ IsBackground = true
+ };
+ t.Start ();
+ }
+ public bool Terminate;
+ string source;
+ Action delRegisterForRepaint;//call RegisterForRepaint in the container widget (DebugInterfaceWidget)
+ Action<Exception> delSetCurrentException;
+
+ void interfaceThread () {
+ while (!Terminate) {
+ try
+ {
+ Update();
+ }
+ catch (System.Exception ex)
+ {
+ if (Monitor.IsEntered(LayoutMutex))
+ Monitor.Exit (LayoutMutex);
+ if (Monitor.IsEntered(UpdateMutex))
+ Monitor.Exit (UpdateMutex);
+ if (Monitor.IsEntered(ClippingMutex))
+ Monitor.Exit (ClippingMutex);
+ delSetCurrentException (ex);
+ ClearInterface();
+ Thread.Sleep(1000);
+ }
+
+ if (IsDirty)
+ delRegisterForRepaint();
+
+ Thread.Sleep (UPDATE_INTERVAL);
+ }
+ }
+ public IntPtr SurfacePointer {
+ get {
+ lock(UpdateMutex)
+ return surf.Handle;
+ }
+ }
+ public void RegisterDebugInterfaceCallback (object w){
+ Type t = w.GetType();
+ delRegisterForRepaint = (Action)Delegate.CreateDelegate(typeof(Action), w, t.GetMethod("RegisterForRepaint"));
+ delSetCurrentException = (Action<Exception>)Delegate.CreateDelegate(typeof(Action<Exception>), w, t.GetProperty("CurrentException").GetSetMethod());
+ }
+ public void ResetDirtyState () {
+ IsDirty = false;
+ }
+ public string Source {
+ set {
+ if (source == value)
+ return;
+ source = value;
+ if (string.IsNullOrEmpty(source))
+ return;
+ delSetCurrentException(null);
+ try
+ {
+ lock (UpdateMutex) {
+ Widget tmp = CreateITorFromIMLFragment (source).CreateInstance();
+ ClearInterface();
+ AddWidget (tmp);
+ tmp.DataSource = this;
+ }
+ }
+ catch (IML.InstantiatorException iTorEx)
+ {
+ delSetCurrentException(iTorEx.InnerException);
+ }
+ catch (System.Exception ex)
+ {
+ delSetCurrentException(ex);
+ }
+ }
+ }
+ public void Resize (int width, int height) {
+
+ lock (UpdateMutex) {
+ clientRectangle = new Rectangle (0, 0, width, height);
+ surf?.Dispose();
+ surf = new ImageSurface (Format.Argb32, width, height);
+ foreach (Widget g in GraphicTree)
+ g.RegisterForLayouting (LayoutingType.All);
+ RegisterClip (clientRectangle);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Samples/DebugLogAnalyzer/src/DebugInterfaceWidget.cs b/Samples/DebugLogAnalyzer/src/DebugInterfaceWidget.cs
new file mode 100644
index 00000000..3f331cf2
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/DebugInterfaceWidget.cs
@@ -0,0 +1,281 @@
+// Copyright (c) 2013-2019 Bruyère Jean-Philippe <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Glfw;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.IO;
+using Crow.Cairo;
+using System.Diagnostics;
+using CrowDbgShared;
+using System.Collections.Generic;
+using Crow.DebugLogger;
+
+namespace Crow
+{
+ public class DebugInterfaceWidget : Widget {
+
+ string imlSource;
+ List<DbgEvent> events;
+ List<DbgWidgetRecord> widgets;
+ Exception currentException;
+ object dbgIFace;
+ AssemblyLoadContext crowLoadCtx;
+ Assembly crowAssembly, thisAssembly;
+ Type dbgIfaceType;
+ Action<int, int> delResize;
+ Func<int, int, bool> delMouseMove;
+ Func<MouseButton, bool> delMouseDown, delMouseUp;
+ Action delResetDirtyState;
+ Action delResetDebugger;
+ Action<object, string> delSaveDebugLog;
+ IntPtrGetterDelegate delGetSurfacePointer;
+ Action<string> delSetSource;
+
+ FieldInfo fiDbg_IncludeEvents, fiDbg_DiscardEvents, fiDbg_ConsoleOutput;
+
+ bool initialized, recording;
+ string crowDbgAssemblyLocation;
+ DbgEvtType recordedEvents, discardedEvents;
+
+ public CommandGroup LoggerCommands =>
+ new CommandGroup(
+ new Command("Get logs", () => getLog ()),
+ new Command("Reset logs", () => delResetDebugger ())
+ );
+ public string IMLSource {
+ get => imlSource;
+ set {
+ if (imlSource == value)
+ return;
+ imlSource = value;
+ if (initialized)
+ delSetSource (imlSource);
+ NotifyValueChangedAuto(imlSource);
+ RegisterForRedraw();
+ }
+ }
+ public Exception CurrentException {
+ get => currentException;
+ set {
+ if (currentException == value)
+ return;
+ currentException = value;
+ NotifyValueChangedAuto(currentException);
+ }
+ }
+ public string CrowDbgAssemblyLocation {
+ get => crowDbgAssemblyLocation;
+ set {
+ if (crowDbgAssemblyLocation == value)
+ return;
+ crowDbgAssemblyLocation = value;
+ NotifyValueChangedAuto(CrowDbgAssemblyLocation);
+ tryStartDebugInterface();
+ }
+ }
+
+ public bool Recording {
+ get => recording;
+ set {
+ if (recording == value)
+ return;
+ recording = value & initialized;
+ if (recording) {
+ fiDbg_DiscardEvents.SetValue (dbgIFace, DiscardedEvents);
+ fiDbg_IncludeEvents.SetValue (dbgIFace, RecordedEvents);
+ } else {
+ fiDbg_DiscardEvents.SetValue (dbgIFace, DiscardedEvents);
+ fiDbg_IncludeEvents.SetValue (dbgIFace, RecordedEvents);
+ }
+ NotifyValueChangedAuto(recording);
+ }
+ }
+ public DbgEvtType RecordedEvents {
+ get => recordedEvents;
+ set {
+ if (recordedEvents == value)
+ return;
+ recordedEvents = value;
+ if (Recording)
+ fiDbg_IncludeEvents.SetValue (dbgIFace, value);
+ NotifyValueChangedAuto (recordedEvents);
+ }
+ }
+ public DbgEvtType DiscardedEvents {
+ get => discardedEvents;
+ set {
+ if (discardedEvents == value)
+ return;
+ discardedEvents = value;
+ if (Recording)
+ fiDbg_DiscardEvents.SetValue (dbgIFace, value);
+ NotifyValueChangedAuto (discardedEvents);
+ }
+ }
+ public bool DebugLogToFile {
+ get => initialized ? !(bool)fiDbg_ConsoleOutput.GetValue (dbgIFace) : false;
+ set {
+ if (!initialized || DebugLogToFile == value)
+ return;
+ fiDbg_ConsoleOutput.SetValue (dbgIFace, !value);
+ NotifyValueChangedAuto (DebugLogToFile);
+ }
+ }
+
+
+ protected override void onInitialized(object sender, EventArgs e)
+ {
+ base.onInitialized(sender, e);
+
+
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ }
+
+ public bool CrowDebuggerOK => initialized;
+ public bool CrowDebuggerNOK => !initialized;
+ void notifyCrowDebuggerState () {
+ NotifyValueChanged("CrowDebuggerOK", CrowDebuggerOK);
+ NotifyValueChanged("CrowDebuggerNOK", CrowDebuggerNOK);
+ }
+
+ void tryStartDebugInterface () {
+ if (initialized)
+ return;
+ if (!File.Exists (crowDbgAssemblyLocation)) {
+ notifyCrowDebuggerState();
+ return;
+ }
+
+ crowLoadCtx = new AssemblyLoadContext("CrowDebuggerLoadContext");
+
+ //using (crowLoadCtx.EnterContextualReflection()) {
+ crowAssembly = crowLoadCtx.LoadFromAssemblyPath (crowDbgAssemblyLocation);
+ thisAssembly = crowLoadCtx.LoadFromAssemblyPath (new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath);
+
+ Type debuggerType = crowAssembly.GetType("Crow.DbgLogger");
+ if (!(bool)debuggerType.GetField("IsEnabled").GetValue(null)) {
+ notifyCrowDebuggerState();
+ return;
+ }
+
+ dbgIfaceType = thisAssembly.GetType("Crow.DebugInterface");
+
+ dbgIFace = Activator.CreateInstance (dbgIfaceType, new object[] {IFace.WindowHandle});
+
+ delResize = (Action<int, int>)Delegate.CreateDelegate(typeof(Action<int, int>),
+ dbgIFace, dbgIfaceType.GetMethod("Resize"));
+
+ delMouseMove = (Func<int, int, bool>)Delegate.CreateDelegate(typeof(Func<int, int, bool>),
+ dbgIFace, dbgIfaceType.GetMethod("OnMouseMove"));
+
+ delMouseDown = (Func<MouseButton, bool>)Delegate.CreateDelegate(typeof(Func<MouseButton, bool>),
+ dbgIFace, dbgIfaceType.GetMethod("OnMouseButtonDown"));
+
+ delMouseUp = (Func<MouseButton, bool>)Delegate.CreateDelegate(typeof(Func<MouseButton, bool>),
+ dbgIFace, dbgIfaceType.GetMethod("OnMouseButtonUp"));
+
+ delGetSurfacePointer = (IntPtrGetterDelegate)Delegate.CreateDelegate(typeof(IntPtrGetterDelegate),
+ dbgIFace, dbgIfaceType.GetProperty("SurfacePointer").GetGetMethod());
+ delSetSource = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>),
+ dbgIFace, dbgIfaceType.GetProperty("Source").GetSetMethod());
+
+ delResetDirtyState = (Action)Delegate.CreateDelegate(typeof(Action),
+ dbgIFace, dbgIfaceType.GetMethod("ResetDirtyState"));
+
+ fiDbg_IncludeEvents = debuggerType.GetField("IncludeEvents");
+ fiDbg_DiscardEvents = debuggerType.GetField("DiscardEvents");
+ fiDbg_ConsoleOutput = debuggerType.GetField("ConsoleOutput");
+ delResetDebugger = (Action)Delegate.CreateDelegate(typeof(Action),
+ null, debuggerType.GetMethod("Reset"));
+ /*delSaveDebugLog = (Action<object, string>)Delegate.CreateDelegate(typeof(Action<object, string>),
+ null, debuggerType.GetMethod("Save", new Type[] {dbgIfaceType, typeof(string)}));*/
+
+ dbgIfaceType.GetMethod("RegisterDebugInterfaceCallback").Invoke (dbgIFace, new object[] {this} );
+ dbgIfaceType.GetMethod("Run").Invoke (dbgIFace, null);
+
+ initialized = true;
+
+ //Console.WriteLine($"DbgIFace: LoadCtx:{AssemblyLoadContext.GetLoadContext (dbgIFace.GetType().Assembly).Name})");
+ //}
+ }
+
+ protected override void onDraw(Context gr)
+ {
+ Console.WriteLine("onDraw");
+ gr.SetSource(Colors.RoyalBlue);
+ gr.Paint();
+ }
+ public override bool CacheEnabled { get => true; set => base.CacheEnabled = true; }
+
+ public override void onMouseMove(object sender, MouseMoveEventArgs e)
+ {
+ if (initialized) {
+ Point m = ScreenPointToLocal (e.Position);
+ delMouseMove (m.X, m.Y);
+ e.Handled = true;
+ }
+ base.onMouseMove(sender, e);
+ }
+ public override void onMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ if (initialized) {
+ delMouseDown (e.Button);
+ e.Handled=true;
+ }
+ base.onMouseDown (sender, e);
+ }
+ public override void onMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ if (initialized) {
+ delMouseUp (e.Button);
+ e.Handled=true;
+ }
+ base.onMouseUp (sender, e);
+ }
+
+ protected override void RecreateCache()
+ {
+ bmp?.Dispose ();
+
+ if (initialized) {
+ delResize (Slot.Width, Slot.Height);
+ bmp = Crow.Cairo.Surface.Lookup (delGetSurfacePointer(), false);
+ } else
+ bmp = IFace.surf.CreateSimilar (Content.ColorAlpha, Slot.Width, Slot.Height);
+
+ IsDirty = false;
+ }
+ protected override void UpdateCache(Context ctx)
+ {
+ if (initialized && bmp != null) {
+ paintCache (ctx, Slot + Parent.ClientRectangle.Position);
+ delResetDirtyState ();
+ }
+
+ }
+
+ void getLog () {
+ using (Stream stream = new MemoryStream (1024)) {
+ Type debuggerType = crowAssembly.GetType("Crow.DbgLogger");
+ debuggerType.GetMethod("Save", new Type[] {dbgIfaceType, typeof(Stream)}).Invoke(null, new object[] {dbgIFace, stream});
+ //debuggerType.GetMethod("Save", new Type[] {dbgIfaceType, typeof(string)}).Invoke(null, new object[] {dbgIFace, "debug.log"});
+ //delSaveDebugLog(dbgIFace, "debug.log");
+ stream.Seek(0, SeekOrigin.Begin);
+ events = new List<DbgEvent>();
+ widgets = new List<DbgWidgetRecord>();
+ DbgLogger.Load (stream, events, widgets);
+ //DbgLogger.Load ("debug.log", events, widgets);
+ DebugLogAnalyzer.Program dla = IFace as DebugLogAnalyzer.Program;
+ dla.Widgets = widgets;
+ dla.Events = events;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Samples/DebugLogAnalyzer/src/Editor.cs b/Samples/DebugLogAnalyzer/src/Editor.cs
new file mode 100644
index 00000000..315a5a9d
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/Editor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2013-2019 Bruyère Jean-Philippe <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Glfw;
+using Crow.Text;
+
+namespace Crow
+{
+ public class Editor : TextBox {
+ public override void onKeyDown(object sender, KeyEventArgs e)
+ {
+ TextSpan selection = Selection;
+ if (e.Key == Key.Tab && !selection.IsEmpty) {
+ int lineStart = lines.GetLocation (selection.Start).Line;
+ int lineEnd = lines.GetLocation (selection.End).Line;
+
+ if (IFace.Shift) {
+ for (int l = lineStart; l <= lineEnd; l++) {
+ if (Text[lines[l].Start] == '\t')
+ update (new TextChange (lines[l].Start, 1, ""));
+ else if (Char.IsWhiteSpace (Text[lines[l].Start])) {
+ int i = 1;
+ while (i < lines[l].Length && i < Interface.TAB_SIZE && Char.IsWhiteSpace (Text[i]))
+ i++;
+ update (new TextChange (lines[l].Start, i, ""));
+ }
+ }
+
+ }else{
+ for (int l = lineStart; l <= lineEnd; l++)
+ update (new TextChange (lines[l].Start, 0, "\t"));
+ }
+
+ selectionStart = new CharLocation (lineStart, 0);
+ CurrentLoc = new CharLocation (lineEnd, lines[lineEnd].Length);
+
+ return;
+ }
+ base.onKeyDown(sender, e);
+ }
+ }
+} \ No newline at end of file
diff --git a/Samples/DebugLogAnalyzer/src/Extensions.cs b/Samples/DebugLogAnalyzer/src/Extensions.cs
new file mode 100644
index 00000000..6086384c
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/Extensions.cs
@@ -0,0 +1,23 @@
+
+
+using Crow;
+
+namespace DebugLogAnalyzer {
+ public static class Extensions {
+
+ public static CommandGroup GetCommands (this System.IO.DirectoryInfo di) =>
+ new CommandGroup(
+ new Command ("Set as root", ()=> {Program.CurrentProgramInstance.CurrentDir = di.FullName;}));
+ public static CommandGroup GetCommands (this System.IO.FileInfo fi) =>
+ new CommandGroup(
+ new Command ("Delete", (sender0) => {
+ MessageBox.ShowModal (Program.CurrentProgramInstance, MessageBox.Type.YesNo, $"Delete {fi.Name}?").Yes += (sender, e) => {
+ System.IO.File.Delete(fi.FullName);
+ Widget listContainer = ((sender0 as Widget).LogicalParent as Widget).DataSource as Widget;
+ (listContainer.Parent as Group).RemoveChild(listContainer);
+ };
+ })
+ );
+
+ }
+} \ No newline at end of file
diff --git a/Samples/DebugLogAnalyzer/src/Program.cs b/Samples/DebugLogAnalyzer/src/Program.cs
new file mode 100644
index 00000000..7da9f5a1
--- /dev/null
+++ b/Samples/DebugLogAnalyzer/src/Program.cs
@@ -0,0 +1,466 @@
+using System.Reflection.PortableExecutable;
+// Copyright (c) 2013-2021 Bruyère Jean-Philippe <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Crow;
+using System.IO;
+using Glfw;
+using Crow.Text;
+using System.Collections.Generic;
+using Encoding = System.Text.Encoding;
+using Crow.DebugLogger;
+
+namespace DebugLogAnalyzer
+{
+ public class Program : SampleBase
+ {
+ public static Program CurrentProgramInstance;
+ static void Main (string [] args)
+ {
+ DbgLogger.IncludeEvents = DbgEvtType.None;
+ DbgLogger.DiscardEvents = DbgEvtType.All;
+
+ using (Program app = new Program ()) {
+ CurrentProgramInstance = app;
+ app.Run ();
+ }
+ }
+ protected override void OnInitialized () {
+ initCommands ();
+
+ base.OnInitialized ();
+
+ if (string.IsNullOrEmpty (CurrentDir))
+ CurrentDir = Path.Combine (Directory.GetCurrentDirectory (), "Interfaces");
+
+ Load ("#Dbg.main.crow").DataSource = this;
+ //crowContainer = FindByName ("CrowContainer") as Container;
+ editor = FindByName ("tb") as TextBox;
+
+ /*TreeView tv = FindByName("dbgTV") as TreeView;
+ dbgTreeViewScroller = tv.FindByNameInTemplate ("scroller1") as Scroller;*/
+
+
+ if (!File.Exists (CurrentFile))
+ newFile ();
+ //I set an empty object as datasource at this level to force update when new
+ //widgets are added to the interface
+
+ reloadFromFile ();
+ }
+
+ List<DbgEvent> events = new List<DbgEvent>();
+ List<DbgWidgetRecord> widgets = new List<DbgWidgetRecord>();
+ DbgEvent curEvent = new DbgEvent();
+ DbgWidgetRecord curWidget = new DbgWidgetRecord();
+ bool debugLogRecording;
+ Scroller dbgTreeViewScroller;
+ int targetTvScroll = -1;
+
+
+
+
+ public Command CMDNew, CMDOpen, CMDSave, CMDSaveAs, CMDQuit, CMDShowLeftPane,
+ CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDHelp, CMDAbout, CMDOptions;
+ public CommandGroup EventCommands, DirectoryCommands;
+ public CommandGroup EditorCommands => new CommandGroup (CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDSave, CMDSaveAs);
+ void initCommands ()
+ {
+ CMDNew = new Command ("New", new Action (onNewFile), "#Icons.blank-file.svg");
+ CMDSave = new Command ("Save", new Action (onSave), "#Icons.save.svg", false);
+ CMDSaveAs = new Command ("Save As...", new Action (onSaveAs), "#Icons.save.svg");
+ CMDQuit = new Command ("Quit", new Action (() => base.Quit ()), "#Icons.exit.svg");
+ CMDUndo = new Command ("Undo", new Action (undo),"#Icons.undo.svg", false);
+ CMDRedo = new Command ("Redo", new Action (redo),"#Icons.redo.svg", false);
+ CMDCut = new Command ("Cut", new Action (() => cut ()), "#Icons.scissors.svg", false);
+ CMDCopy = new Command ("Copy", new Action (() => copy ()), "#Icons.copy-file.svg", false);
+ CMDPaste= new Command ("Paste", new Action (() => paste ()), "#Icons.paste-on-document.svg", false);
+
+ EventCommands = new CommandGroup(
+ new Command("Goto parent event", ()=> { CurrentEvent = CurrentEvent?.parentEvent; })
+ );
+ DirectoryCommands = new CommandGroup(
+ new Command("Set as root directory", ()=> { CurrentEvent = CurrentEvent?.parentEvent; })
+ );
+
+ }
+
+
+
+
+
+ public string CrowDbgAssemblyLocation {
+ get => Configuration.Global.Get<string>("CrowDbgAssemblyLocation");
+ set {
+ if (CrowDbgAssemblyLocation == value)
+ return;
+ Configuration.Global.Set ("CrowDbgAssemblyLocation", value);
+ NotifyValueChanged(CrowDbgAssemblyLocation);
+ }
+ }
+ public List<DbgEvent> Events {
+ get => events;
+ set {
+ if (events == value)
+ return;
+ events = value;
+ NotifyValueChanged (nameof (Events), events);
+ }
+ }
+ public List<DbgWidgetRecord> Widgets {
+ get => widgets;
+ set {
+ if (widgets == value)
+ return;
+ widgets = value;
+ NotifyValueChanged (nameof (Widgets), widgets);
+ }
+ }
+ public DbgEvent CurrentEvent {
+ get => curEvent;
+ set {
+ if (curEvent == value)
+ return;
+
+ curEvent = value;
+ NotifyValueChanged (nameof (CurrentEvent), curEvent);
+ NotifyValueChanged ("CurEventChildEvents", curEvent?.Events);
+
+ }
+ }
+ public DbgWidgetRecord CurrentWidget {
+ get => curWidget;
+ set {
+ if (curWidget == value)
+ return;
+ curWidget = value;
+ NotifyValueChanged (nameof (CurrentWidget), curWidget);
+ NotifyValueChanged ("CurWidgetRootEvents", curWidget?.RootEvents);
+ NotifyValueChanged ("CurWidgetEvents", curWidget?.Events);
+
+ }
+ }
+ public List<DbgWidgetEvent> CurWidgetRootEvents => curWidget == null? new List<DbgWidgetEvent>() : curWidget.RootEvents;
+ public DbgEvtType RecordedEvents {
+ get => Configuration.Global.Get<DbgEvtType> (nameof (RecordedEvents));
+ set {
+ if (RecordedEvents == value)
+ return;
+ Configuration.Global.Set (nameof (RecordedEvents), value);
+ if (DebugLogRecording)
+ DbgLogger.IncludeEvents = RecordedEvents;
+ NotifyValueChanged(RecordedEvents);
+ }
+ }
+ public DbgEvtType DiscardedEvents {
+ get => Configuration.Global.Get<DbgEvtType> (nameof (DiscardedEvents));
+ set {
+ if (DiscardedEvents == value)
+ return;
+ Configuration.Global.Set (nameof (DiscardedEvents), value);
+ if (DebugLogRecording)
+ DbgLogger.DiscardEvents = DiscardedEvents;
+ NotifyValueChanged(DiscardedEvents);
+ }
+ }
+ public bool DebugLoggingEnabled => DbgLogger.IsEnabled;
+ public bool DebugLogToFile {
+ get => Configuration.Global.Get<bool> (nameof(DebugLogToFile));
+ set {
+ if (DbgLogger.ConsoleOutput != value)
+ return;
+ Configuration.Global.Set (nameof(DebugLogToFile), value);
+ NotifyValueChanged(DebugLogToFile);
+ }
+ }
+ public string DebugLogFilePath {
+ get => Configuration.Global.Get<string> (nameof (DebugLogFilePath));
+ set {
+ if (CurrentFile == value)
+ return;
+ Configuration.Global.Set (nameof (DebugLogFilePath), value);
+ NotifyValueChanged (DebugLogFilePath);
+ }
+ }
+ public bool DebugLogRecording {
+ get => debugLogRecording;
+ set {
+ if (debugLogRecording == value)
+ return;
+ debugLogRecording = value;
+ NotifyValueChanged(debugLogRecording);
+ }
+ }
+
+
+ const string _defaultFileName = "unnamed.txt";
+ string source = "", origSource;
+ TextBox editor;
+ Stack<TextChange> undoStack = new Stack<TextChange> ();
+ Stack<TextChange> redoStack = new Stack<TextChange> ();
+ TextSpan selection;
+ Exception currentException;
+ public string CurrentDir {
+ get => Configuration.Global.Get<string> (nameof (CurrentDir));
+ set {
+ if (CurrentDir == value)
+ return;
+ Configuration.Global.Set (nameof (CurrentDir), value);
+ NotifyValueChanged (CurrentDir);
+ }
+ }
+ public string CurrentFile {
+ get => Configuration.Global.Get<string> (nameof (CurrentFile));
+ set {
+ if (CurrentFile == value)
+ return;
+ Configuration.Global.Set (nameof (CurrentFile), value);
+ NotifyValueChanged (CurrentFile);
+ }
+ }
+ public new bool IsDirty => source != origSource;
+ public string Source {
+ get => source;
+ set {
+ if (source == value)
+ return;
+ source = value;
+ CMDSave.CanExecute = IsDirty;
+ NotifyValueChanged (source);
+ NotifyValueChanged ("IsDirty", IsDirty);
+ }
+ }
+ string SelectedText =>
+ selection.IsEmpty ? "" : Source.AsSpan (selection.Start, selection.Length).ToString ();
+ public Exception CurrentException {
+ get => currentException;
+ set {
+ if (currentException == value)
+ return;
+ currentException = value;
+ NotifyValueChanged ("ShowError", ShowError);
+ NotifyValueChanged ("CurrentExceptionMSG", (object)CurrentExceptionMSG);
+ NotifyValueChanged (currentException);
+ }
+ }
+ public bool ShowError => currentException != null;
+ public string CurrentExceptionMSG => currentException == null ? "" : currentException.Message;
+
+
+
+ void undo () {
+ if (undoStack.TryPop (out TextChange tch)) {
+ redoStack.Push (tch.Inverse (source));
+ CMDRedo.CanExecute = true;
+ apply (tch);
+ editor.SetCursorPosition (tch.End + tch.ChangedText.Length);
+ }
+ if (undoStack.Count == 0)
+ CMDUndo.CanExecute = false;
+ }
+ void redo () {
+ if (redoStack.TryPop (out TextChange tch)) {
+ undoStack.Push (tch.Inverse (source));
+ CMDUndo.CanExecute = true;
+ apply (tch);
+ editor.SetCursorPosition (tch.End + tch.ChangedText.Length);
+ }
+ if (redoStack.Count == 0)
+ CMDRedo.CanExecute = false;
+ }
+ void cut () {
+ copy ();
+ applyChange (new TextChange (selection.Start, selection.Length, ""));
+ }
+ void copy () {
+ Clipboard = SelectedText;
+ }
+ void paste () {
+ applyChange (new TextChange (selection.Start, selection.Length, Clipboard));
+ }
+ bool disableTextChangedEvent = false;
+ void apply (TextChange change) {
+ Span<char> tmp = stackalloc char[source.Length + (change.ChangedText.Length - change.Length)];
+ ReadOnlySpan<char> src = source.AsSpan ();
+ src.Slice (0, change.Start).CopyTo (tmp);
+ if (!string.IsNullOrEmpty (change.ChangedText))
+ change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start));
+ src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length));
+ disableTextChangedEvent = true;
+ Source = tmp.ToString ();
+ disableTextChangedEvent = false;
+ }
+ void applyChange (TextChange change) {
+ undoStack.Push (change.Inverse (source));
+ redoStack.Clear ();
+ CMDUndo.CanExecute = true;
+ CMDRedo.CanExecute = false;
+ apply (change);
+ }
+
+ void resetUndoRedo () {
+ undoStack.Clear ();
+ redoStack.Clear ();
+ CMDUndo.CanExecute = false;
+ CMDRedo.CanExecute = false;
+ }
+
+ void onNewFile () {
+ if (IsDirty) {
+ MessageBox.ShowModal (this, MessageBox.Type.YesNo, "Current file has unsaved changes, are you sure?").Yes += (sender, e) => newFile ();
+ } else
+ newFile ();
+ }
+ void onSave ()
+ {
+ if (!File.Exists (CurrentFile)) {
+ onSaveAs ();
+ return;
+ }
+ save ();
+ }
+ void onSaveAs ()
+ {
+ string dir = Path.GetDirectoryName (CurrentFile);
+ if (string.IsNullOrEmpty (dir))
+ dir = Directory.GetCurrentDirectory ();
+ LoadIMLFragment (@"<FileDialog Width='60%' Height='50%' Caption='Save as ...' CurrentDirectory='" +
+ dir + "' SelectedFile='" +
+ Path.GetFileName(CurrentFile) + "' OkClicked='saveFileDialog_OkClicked'/>").DataSource = this;
+ }
+ void saveFileDialog_OkClicked (object sender, EventArgs e)
+ {
+ FileDialog fd = sender as FileDialog;
+
+ if (string.IsNullOrEmpty (fd.SelectedFileFullPath))
+ return;
+
+ if (File.Exists(fd.SelectedFileFullPath)) {
+ MessageBox mb = MessageBox.ShowModal (this, MessageBox.Type.YesNo, "File exists, overwrite?");
+ mb.Yes += (sender2, e2) => {
+ CurrentFile = fd.SelectedFileFullPath;
+ save ();
+ };
+ return;
+ }
+
+ CurrentFile = fd.SelectedFileFullPath;
+ save ();
+ }
+
+ void newFile()
+ {
+ disableTextChangedEvent = true;
+ Source = @"<Label Text='Hello World' Background='MediumSeaGreen' Margin='10'/>";
+ disableTextChangedEvent = false;
+ resetUndoRedo ();
+ if (!string.IsNullOrEmpty (CurrentFile))
+ CurrentFile = Path.Combine (Path.GetDirectoryName (CurrentFile), "newfile.crow");
+ else
+ CurrentFile = Path.Combine (CurrentDir, "newfile.crow");
+ }
+
+ void save () {
+ using (Stream s = new FileStream(CurrentFile, FileMode.Create)) {
+ s.WriteByte (0xEF);
+ s.WriteByte (0xBB);
+ s.WriteByte (0xBF);
+ byte [] buff = Encoding.UTF8.GetBytes (source);
+ s.Write (buff, 0, buff.Length);
+ }
+ origSource = source;
+ NotifyValueChanged ("IsDirty", IsDirty);
+ CMDSave.CanExecute = false;
+ }
+
+ void reloadFromFile () {
+ disableTextChangedEvent = true;
+ if (File.Exists (CurrentFile)) {
+ using (Stream s = new FileStream (CurrentFile, FileMode.Open)) {
+ using (StreamReader sr = new StreamReader (s))
+ Source = origSource = sr.ReadToEnd ();
+ }
+ }
+ disableTextChangedEvent = false;
+ resetUndoRedo ();
+ }
+
+ public void goUpDirClick (object sender, MouseButtonEventArgs e)
+ {
+ string root = Directory.GetDirectoryRoot (CurrentDir);
+ if (CurrentDir == root)
+ return;
+ CurrentDir = Directory.GetParent (CurrentDir).FullName;
+ }
+
+ void Dv_SelectedItemChanged (object sender, SelectionChangeEventArgs e)
+ {
+ FileSystemInfo fi = e.NewValue as FileSystemInfo;
+ if (fi == null)
+ return;
+ if (fi is DirectoryInfo)
+ return;
+
+ if (IsDirty) {
+ MessageBox mb = MessageBox.ShowModal (this, MessageBox.Type.YesNo, "Current file has unsaved changes, are you sure?");
+ mb.Yes += (mbsender, mbe) => { CurrentFile = fi.FullName; reloadFromFile (); };
+ return;
+ }
+
+ CurrentFile = fi.FullName;
+ reloadFromFile ();
+ }
+ void onTextChanged (object sender, TextChangeEventArgs e) {
+ if (disableTextChangedEvent)
+ return;
+ applyChange (e.Change);
+ }
+
+ void onSelectedTextChanged (object sender, EventArgs e) {
+ selection = (sender as Label).Selection;
+ Console.WriteLine($"selection:{selection.Start} length:{selection.Length}");
+ CMDCut.CanExecute = CMDCopy.CanExecute = !selection.IsEmpty;
+ }
+ void textView_KeyDown (object sender, Crow.KeyEventArgs e) {
+ if (Ctrl) {
+ if (e.Key == Glfw.Key.W) {
+ if (Shift)
+ CMDRedo.Execute ();
+ else
+ CMDUndo.Execute ();
+ } else if (e.Key == Glfw.Key.S) {
+ onSave ();
+ }
+ }
+ }
+
+
+
+ public override bool OnKeyDown (Key key) {
+
+ switch (key) {
+ case Key.F5:
+ Load ("#Dbg.DebugLog.crow").DataSource = this;
+ return true;
+ /*case Key.F6:
+ if (DebugLogRecording) {
+ DbgLogger.IncludeEvents = DbgEvtType.None;
+ DbgLogger.DiscardEvents = DbgEvtType.All;
+ if (DebugLogToFile && !string.IsNullOrEmpty(DebugLogFilePath))
+ DbgLogger.Save (this, DebugLogFilePath);
+ DebugLogRecording = false;
+ } else {
+ DbgLogger.Reset ();
+ DbgLogger.IncludeEvents = RecordedEvents;
+ DbgLogger.DiscardEvents = DiscardedEvents;
+ DebugLogRecording = true;
+ }
+ return true;*/
+ }
+ return base.OnKeyDown (key);
+ }
+ }
+}
+