Преглед изворни кода

Merge branch 'master' of https://github.com/taylor001/crown

Daniele Bartolini пре 11 година
родитељ
комит
5e41cc9586
39 измењених фајлова са 1201 додато и 291 уклоњено
  1. 0 1
      third/ARMv7/luajit/bin/luajit
  2. 1 0
      third/ARMv7/luajit/bin/luajit
  3. 0 1
      third/ARMv7/luajit/lib/libluajit-5.1.so
  4. 1 0
      third/ARMv7/luajit/lib/libluajit-5.1.so
  5. 0 1
      third/x86/luajit/bin/luajit
  6. 1 0
      third/x86/luajit/bin/luajit
  7. 0 1
      third/x86/luajit/lib/libluajit-5.1.so
  8. 1 0
      third/x86/luajit/lib/libluajit-5.1.so
  9. 0 1
      third/x86/luajit/lib/libluajit-5.1.so.2
  10. 1 0
      third/x86/luajit/lib/libluajit-5.1.so.2
  11. 0 1
      third/x86_64/luajit/bin/luajit
  12. 1 0
      third/x86_64/luajit/bin/luajit
  13. 0 1
      third/x86_64/luajit/lib/libluajit-5.1.so
  14. 1 0
      third/x86_64/luajit/lib/libluajit-5.1.so
  15. 0 1
      third/x86_64/luajit/lib/libluajit-5.1.so.2
  16. 1 0
      third/x86_64/luajit/lib/libluajit-5.1.so.2
  17. 0 1
      tools/gui/console/console.csproj
  18. 117 0
      tools/gui/crown-tests/GtkExt/BindingEngine.cs
  19. 39 0
      tools/gui/crown-tests/GtkExt/BindingTargets/EntryBindingTarget.cs
  20. 43 0
      tools/gui/crown-tests/GtkExt/BindingTargets/TreeViewIterBindingTarget.cs
  21. 35 0
      tools/gui/crown-tests/GtkExt/EntryTemplate.cs
  22. 20 0
      tools/gui/crown-tests/GtkExt/IPropertyChanged.cs
  23. 39 0
      tools/gui/crown-tests/GtkExt/Templating.cs
  24. 34 0
      tools/gui/crown-tests/GtkExt/TreeViewRowTemplate.cs
  25. 70 0
      tools/gui/crown-tests/GtkExt/TreeViewTemplate.cs
  26. 33 0
      tools/gui/crown-tests/GtkExt/ViewModelBase.cs
  27. 90 113
      tools/gui/crown-tests/MainWindow.cs
  28. 10 10
      tools/gui/crown-tests/Program.cs
  29. 27 0
      tools/gui/crown-tests/ViewModels/CrownTestsViewModel.cs
  30. 25 1
      tools/gui/crown-tests/crown-tests.csproj
  31. 170 0
      tools/gui/crown-tests/gtk-gui/crown_tests.MainWindow.cs
  32. 29 0
      tools/gui/crown-tests/gtk-gui/generated.cs
  33. 221 0
      tools/gui/crown-tests/gtk-gui/gui.stetic
  34. 30 13
      tools/gui/crown-tests/tests/Test.cs
  35. 17 16
      tools/gui/crown-tests/tests/TestCategory.cs
  36. 10 10
      tools/gui/crown-tests/tests/TestContainer.cs
  37. 24 26
      tools/gui/crown-tests/tests/TestExecutor.cs
  38. 74 80
      tools/gui/crown-tests/tests/TestSourceCreator.cs
  39. 36 13
      tools/gui/toolbox.sln

+ 0 - 1
third/ARMv7/luajit/bin/luajit

@@ -1 +0,0 @@
-luajit-2.0.2

+ 1 - 0
third/ARMv7/luajit/bin/luajit

@@ -0,0 +1 @@
+luajit-2.0.2

+ 0 - 1
third/ARMv7/luajit/lib/libluajit-5.1.so

@@ -1 +0,0 @@
-libluajit-5.1.so.2.0.2

+ 1 - 0
third/ARMv7/luajit/lib/libluajit-5.1.so

@@ -0,0 +1 @@
+libluajit-5.1.so.2.0.2

+ 0 - 1
third/x86/luajit/bin/luajit

@@ -1 +0,0 @@
-luajit-2.0.2

+ 1 - 0
third/x86/luajit/bin/luajit

@@ -0,0 +1 @@
+luajit-2.0.2

+ 0 - 1
third/x86/luajit/lib/libluajit-5.1.so

@@ -1 +0,0 @@
-libluajit-5.1.so.2.0.2

+ 1 - 0
third/x86/luajit/lib/libluajit-5.1.so

@@ -0,0 +1 @@
+libluajit-5.1.so.2.0.2

+ 0 - 1
third/x86/luajit/lib/libluajit-5.1.so.2

@@ -1 +0,0 @@
-libluajit-5.1.so.2.0.2

+ 1 - 0
third/x86/luajit/lib/libluajit-5.1.so.2

@@ -0,0 +1 @@
+libluajit-5.1.so.2.0.2

+ 0 - 1
third/x86_64/luajit/bin/luajit

@@ -1 +0,0 @@
-luajit-2.0.1

+ 1 - 0
third/x86_64/luajit/bin/luajit

@@ -0,0 +1 @@
+luajit-2.0.1

+ 0 - 1
third/x86_64/luajit/lib/libluajit-5.1.so

@@ -1 +0,0 @@
-libluajit-5.1.so.2.0.1

+ 1 - 0
third/x86_64/luajit/lib/libluajit-5.1.so

@@ -0,0 +1 @@
+libluajit-5.1.so.2.0.1

+ 0 - 1
third/x86_64/luajit/lib/libluajit-5.1.so.2

@@ -1 +0,0 @@
-libluajit-5.1.so.2.0.1

+ 1 - 0
third/x86_64/luajit/lib/libluajit-5.1.so.2

@@ -0,0 +1 @@
+libluajit-5.1.so.2.0.1

+ 0 - 1
tools/gui/console/console.csproj

@@ -53,7 +53,6 @@
     <Reference Include="Mono.Posix" />
     <Reference Include="Newtonsoft.Json">
       <HintPath>..\..\..\..\..\Desktop\Net35\Newtonsoft.Json.dll</HintPath>
-      <Package>monodevelop</Package>
     </Reference>
   </ItemGroup>
   <ItemGroup>

+ 117 - 0
tools/gui/crown-tests/GtkExt/BindingEngine.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+
+namespace crown_tests.GtkExt
+{
+	public static class BindingEngine
+	{
+		private static Dictionary<IBindingTarget, Binding> mBindings;
+		private static Dictionary<Gtk.Widget, ViewModelBase> mViewModels;
+
+		static BindingEngine() {
+			mBindings = new Dictionary<IBindingTarget, Binding>();
+			mViewModels = new Dictionary<Gtk.Widget, ViewModelBase>();
+		}
+
+		public static void SetViewModel(Gtk.Widget widget, ViewModelBase viewModel)
+		{
+			if (mViewModels.ContainsKey(widget)) {
+				Console.WriteLine("BindingEngine.SetViewModel: ViewModel already set for this widget");
+				return;
+			}
+			mViewModels.Add(widget, viewModel);
+		}
+
+		public static ViewModelBase GetViewModel(Gtk.Widget widget)
+		{
+			var currWidget = widget;
+			while (currWidget != null) {
+				ViewModelBase viewModel = null;
+				if (mViewModels.TryGetValue(currWidget, out viewModel))
+					return viewModel;
+
+				currWidget = currWidget.Parent;
+			}
+
+			return null;
+		}
+
+		public static Binding GetOrCreateBinding(Gtk.Widget widget, Object source, IBindingTarget target, BindingInfo info) 
+		{
+			Binding result = null;
+			if (!mBindings.TryGetValue(target, out result)) {
+				result = new Binding();
+				result.Info = info;
+				result.Connect(widget, source, target);
+				mBindings[target] = result;
+			}
+			return result;
+		}
+	}
+
+	//BindingInfo holds binding informations shared among all Bindings of the same type. For example, a TreeViewRowTemplate that is applied to 
+	//multiple rows has a BindingInfo for each column, that is shared among Binding instance for single rows.
+	public class BindingInfo
+	{
+		public String Path;
+		public Func<object, object> Converter;
+	}
+
+	//A Binding si an association between two properties. Usually the source is a ViewModel property and the Target a View property. 
+	//The Source and the Target are determined based on the context of application: When inserted in a Template, upon first application the two
+	//are determined and set, attaching necessary event handlers to manage the update when one of them changes.
+	public class Binding
+	{
+		public BindingInfo Info;
+
+		private Object mSource;
+		private IBindingTarget mTarget;
+
+		public void Connect(Gtk.Widget widget, Object source, IBindingTarget target) {
+			if (source == null) {
+				mSource = BindingEngine.GetViewModel(widget);
+				if (mSource == null) {
+					Console.WriteLine("Binding.Connect: ViewModel not set for this widget");
+					return;
+				}
+			} else {
+				mSource = source;
+			}
+
+			mTarget = target;
+
+			var propertyChanged = mSource as IPropertyChanged;
+			if (propertyChanged != null) {
+				propertyChanged.PropertyChanged += (object sender, PropertyChangedEventArgs e) => mTarget.Update(this, GetSourceValue());
+			}
+		}
+
+		public Object GetSourceValue() {
+			if (mSource == null)
+				return null;
+
+			var propInfo = mSource.GetType().GetProperty(Info.Path);
+			if (propInfo == null)
+				return null;
+
+			var propValue = propInfo.GetValue(mSource, null);
+			if (Info.Converter != null)
+				propValue = Info.Converter(propValue);
+			return propValue;
+		}
+	}
+
+	public class BindingTargetValueChangedEventArgs: EventArgs
+	{
+		public Object NewValue;
+	}
+	public delegate void BindingTargetValueChangedEventHandler(object sender, BindingTargetValueChangedEventArgs e);
+
+	public interface IBindingTarget
+	{
+
+		void Update(Binding binding, Object newValue);
+		event BindingTargetValueChangedEventHandler ValueChanged;
+	}
+}
+

+ 39 - 0
tools/gui/crown-tests/GtkExt/BindingTargets/EntryBindingTarget.cs

@@ -0,0 +1,39 @@
+using System;
+
+namespace crown_tests.GtkExt
+{
+	sealed class EntryBindingTarget: IBindingTarget
+	{
+		private Gtk.Entry mEntry;
+
+		public EntryBindingTarget(Gtk.Entry Entry)
+		{
+			mEntry = Entry;
+		}
+
+		public override bool Equals(object obj)
+		{
+			var other = obj as EntryBindingTarget;
+			if (other == null)
+				return false;
+			return (mEntry == other.mEntry);
+		}
+
+		public override int GetHashCode()
+		{
+			return mEntry.GetHashCode();
+		}
+
+		#region IBindingTarget implementation
+
+		public event BindingTargetValueChangedEventHandler ValueChanged;
+
+		public void Update(Binding binding, object newValue)
+		{
+			mEntry.Text = newValue == null ? String.Empty : newValue.ToString();
+		}
+
+		#endregion
+	}
+}
+

+ 43 - 0
tools/gui/crown-tests/GtkExt/BindingTargets/TreeViewIterBindingTarget.cs

@@ -0,0 +1,43 @@
+using System;
+
+namespace crown_tests.GtkExt
+{
+	sealed class TreeViewIterBindingTarget: IBindingTarget
+	{
+		private Gtk.TreeIter mIter;
+		private Gtk.TreeView mTreeView;
+		private Gtk.TreeViewColumn mColumn;
+
+		public TreeViewIterBindingTarget(Gtk.TreeView treeView, Gtk.TreeIter iter, Gtk.TreeViewColumn column)
+		{
+			mTreeView = treeView;
+			mIter = iter;
+			mColumn = column;
+		}
+
+		public override bool Equals(object obj)
+		{
+			var other = obj as TreeViewIterBindingTarget;
+			if (other == null)
+				return false;
+			return other.mIter.Equals(mIter) && (mTreeView == other.mTreeView) && (mColumn == other.mColumn);
+		}
+
+		public override int GetHashCode()
+		{
+			return mIter.GetHashCode();
+		}
+
+		#region IBindingTarget implementation
+
+		public event BindingTargetValueChangedEventHandler ValueChanged;
+
+		public void Update(Binding binding, object newValue)
+		{
+			mTreeView.Model.EmitRowChanged(mTreeView.Model.GetPath(mIter), mIter);
+		}
+
+		#endregion
+	}
+}
+

+ 35 - 0
tools/gui/crown-tests/GtkExt/EntryTemplate.cs

@@ -0,0 +1,35 @@
+using System;
+
+namespace crown_tests.GtkExt
+{
+	public class EntryTemplate: ITemplate
+	{
+		private BindingInfo mTextBinding;
+
+		public EntryTemplate()
+		{
+		}
+
+		public EntryTemplate SetTextBinding(String path)
+		{
+			mTextBinding = new BindingInfo() { Path = path };
+			return this;
+		}
+
+		public void Apply(Gtk.Widget widget) 
+		{
+			Gtk.Entry entry = widget as Gtk.Entry;
+			if (entry == null) {
+				Console.WriteLine("EntryTemplate.Apply: Invalid widget type for this template");
+				return;
+			}
+
+			if (mTextBinding != null) {
+				var bindingTarget = new EntryBindingTarget(entry);
+				Binding binding = BindingEngine.GetOrCreateBinding(widget, null, bindingTarget, mTextBinding);
+				bindingTarget.Update(binding, binding.GetSourceValue());
+			}
+		}
+	}
+}
+

+ 20 - 0
tools/gui/crown-tests/GtkExt/IPropertyChanged.cs

@@ -0,0 +1,20 @@
+using System;
+
+namespace crown_tests.GtkExt
+{
+	public class PropertyChangedEventArgs: EventArgs
+	{
+		public readonly String PropertyName;
+
+		public PropertyChangedEventArgs(String propName)
+		{
+			PropertyName = propName;
+		}
+	}
+	public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
+	public interface IPropertyChanged
+	{
+		event PropertyChangedEventHandler PropertyChanged;
+	}
+}
+

+ 39 - 0
tools/gui/crown-tests/GtkExt/Templating.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+
+namespace crown_tests.GtkExt
+{
+	public interface ITemplate
+	{
+		void Apply(Gtk.Widget widget);
+	}
+
+	public static class Templating
+	{
+		private static Dictionary<Gtk.Widget, ITemplate> mTemplates;
+
+		static Templating()
+		{
+			mTemplates = new Dictionary<Gtk.Widget, ITemplate>();
+		}
+
+		public static ITemplate GetTemplate(Gtk.Widget widget)
+		{
+			ITemplate template = null;
+			mTemplates.TryGetValue(widget, out template);
+			return template;
+		}
+
+		public static void ApplyTemplate(Gtk.Widget widget, ITemplate template)
+		{
+			if (GetTemplate(widget) != null) {
+				Console.WriteLine("Templating.ApplyTemplate: template already applied for this widget");
+				return;
+			}
+
+			mTemplates.Add(widget, template);
+			template.Apply(widget);
+		}
+	}
+}
+

+ 34 - 0
tools/gui/crown-tests/GtkExt/TreeViewRowTemplate.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+
+namespace crown_tests.GtkExt
+{
+	public class TreeViewRowTemplate
+	{
+		public Type TargetType;
+		public Dictionary<String, BindingInfo> ColumnBindings;
+
+		public TreeViewRowTemplate(Type targetType)
+		{
+			this.TargetType = targetType;
+			this.ColumnBindings = new Dictionary<string, BindingInfo>();
+		}
+
+		public static TreeViewRowTemplate Create(Type targetType)
+		{
+			return new TreeViewRowTemplate(targetType);
+		}
+
+		public TreeViewRowTemplate SetBinding(String colName, String path)
+		{
+			ColumnBindings.Add(colName, new BindingInfo() { Path = path });
+			return this;
+		}
+
+		public TreeViewRowTemplate SetBinding(String colName, String path, Func<object, object> Converter)
+		{
+			ColumnBindings.Add(colName, new BindingInfo() { Path = path, Converter = Converter });
+			return this;
+		}
+	}
+}

+ 70 - 0
tools/gui/crown-tests/GtkExt/TreeViewTemplate.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+
+namespace crown_tests.GtkExt
+{
+	public class TreeViewTemplate: ITemplate
+	{
+		private List<TreeViewRowTemplate> RowTemplates = new List<TreeViewRowTemplate>();
+		private List<Tuple<String, Gtk.CellRenderer>> Columns = new List<Tuple<String, Gtk.CellRenderer>>();
+
+		public TreeViewTemplate()
+		{
+		}
+
+		public TreeViewTemplate AddColumn(String Title, Gtk.CellRenderer renderer)
+		{
+			Columns.Add(Tuple.Create(Title, renderer));
+			return this;
+		}
+
+		public TreeViewTemplate AddRowTemplate(TreeViewRowTemplate rowTemplate)
+		{
+			RowTemplates.Add(rowTemplate);
+			return this;
+		}
+
+		public void Apply(Gtk.Widget widget) 
+		{
+			Gtk.TreeView treeView = widget as Gtk.TreeView;
+			if (treeView == null) {
+				Console.WriteLine("TreeViewTemplate.Apply: Invalid widget type for this template");
+				return;
+			}
+
+			foreach (var col in Columns) {
+				treeView.AppendColumn(col.Item1, col.Item2, ValuePropertyDataFunc);
+			}
+		}
+
+		private static void ValuePropertyDataFunc(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
+		{
+			var treeView = (Gtk.TreeView)column.TreeView;
+			var info = Templating.GetTemplate(treeView) as TreeViewTemplate;
+
+			var textCell = (cell as Gtk.CellRendererText);
+			textCell.Text = string.Empty;
+			var value = model.GetValue(iter, 0);
+			if (value == null)
+				return;
+
+			foreach (var rowTemplate in info.RowTemplates) {
+				if (value.GetType() == rowTemplate.TargetType) {
+					//Here we have a value, which is the source for Binding, and a BindingInfo that is given by rowTemplate.ColumnBindings[column.Title] .
+					//The instance of the BindingInfo is shared among all values (rows), since it was defined once in the rowTemplate.
+
+					BindingInfo bindingInfo = null;
+					if (!rowTemplate.ColumnBindings.TryGetValue(column.Title, out bindingInfo))
+						return;
+
+					//The actual binding, on the other hand, is specific to the current (row,column) pair.
+					Binding binding = BindingEngine.GetOrCreateBinding(treeView, value, new TreeViewIterBindingTarget(treeView, iter, column), bindingInfo);
+					var propValue = binding.GetSourceValue();
+					textCell.Text = propValue == null ? String.Empty : propValue.ToString();
+					return;
+				}
+			}
+		}
+	}
+}
+

+ 33 - 0
tools/gui/crown-tests/GtkExt/ViewModelBase.cs

@@ -0,0 +1,33 @@
+using System;
+
+namespace crown_tests.GtkExt
+{
+	public class ViewModelBase: IPropertyChanged
+	{
+		public ViewModelBase()
+		{
+		}
+
+		protected Boolean SetAndNotify<T>(ref T field, T newValue, String propertyName) {
+			if (!object.Equals(field, newValue)) {
+				field = newValue;
+				Notify(propertyName);
+				return true;
+			}
+			return false;
+		}
+
+		#region IPropertyChanged implementation
+
+		public event PropertyChangedEventHandler PropertyChanged;
+
+		protected void Notify(String propertyName)
+		{
+			if (PropertyChanged != null)
+				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+		}
+
+		#endregion
+	}
+}
+

+ 90 - 113
tools/gui/crown-tests/MainWindow.cs

@@ -1,118 +1,95 @@
 using System;
 using Gtk;
-using Newtonsoft.Json;
 using crown_tests.tests;
+using Newtonsoft.Json;
+using crown_tests.GtkExt;
+using crown_tests.ViewModels;
 
-public partial class MainWindow : Gtk.Window
+namespace crown_tests
 {
-  Gtk.TreeStore mTreeStore;
-  Gtk.TreeView mTreeView;
-  Gtk.Entry mEntryTestFolder;
-  Gtk.Entry mEntryCrownTestsExe;
-
-  private TestContainer mContainer;
-
-  public MainWindow(): base(Gtk.WindowType.Toplevel)
-  {
-    //Glade.XML gxml = new Glade.XML("main1.glade", "MyWindow", null);
-    //gxml.Autoconnect(this);
-    Title = "Test Browser";
-    SetSizeRequest(500, 300);
-
-    var table = new Gtk.Table(2, 2, false);
-
-    var configTable = new Gtk.Table(1, 1, false);
-    configTable.RowSpacing = 2;
-    configTable.ColumnSpacing = 2;
-    mEntryTestFolder = new Gtk.Entry();
-    configTable.Attach(mEntryTestFolder, 1, 2, 0, 1);
-    mEntryCrownTestsExe = new Gtk.Entry();
-    configTable.Attach(mEntryCrownTestsExe, 1, 2, 1, 2);
-    var label1 = new Gtk.Label("Tests folder");
-    configTable.Attach(label1, 0, 1, 0, 1, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
-    var label2 = new Gtk.Label("crown-tests executable");
-    configTable.Attach(label2, 0, 1, 1, 2, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
-    table.Attach(configTable, 0, 2, 0, 1, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Shrink, 10, 10);
-    
-    mTreeView = new Gtk.TreeView();
-    mTreeView.AppendColumn("Name", new Gtk.CellRendererText(), "text", 0);
-    mTreeView.AppendColumn("State", new Gtk.CellRendererText(), "text", 1);
-    //treeview1.AppendColumn("Description", new Gtk.CellRendererText(), "text", 1);
-    
-    table.Attach(mTreeView, 0, 1, 1, 2);
-
-    var frameAlign = new Gtk.Alignment(0, 0, 0, 0);
-    var frame = new Gtk.Frame("Operations");
-    var frameContentVBox = new Gtk.VBox();
-    frame.Child = frameContentVBox;
-    frame.WidthRequest = 120;
-
-    Gtk.Button btnCreate = new Gtk.Button();
-    btnCreate.Label = "Create";
-    btnCreate.Clicked += btnCreate_Click;
-    //table.Attach(btnCreate, 1, 2, 1, 2, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
-    frameContentVBox.PackStart(btnCreate);
-
-    Gtk.Button btnExecute = new Gtk.Button();
-    btnExecute.Label = "Execute";
-    btnExecute.Clicked += btnExecute_Click;
-    //table.Attach(btnExecute, 1, 2, 2, 3, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
-    frameContentVBox.PackStart(btnExecute);
-    frameAlign.Child = frame;
-    table.Attach(frameAlign, 1, 2, 1, 2, AttachOptions.Shrink, AttachOptions.Expand | AttachOptions.Fill, 10, 0);
-
-    Add(table);
-
-    LoadConfigData();
-    LoadTestsData();
-  }
-
-  private void btnCreate_Click(object o, EventArgs args)
-  {
-    var creator = new TestSourceCreator(mContainer, mEntryTestFolder.Text);
-    creator.Create();
-  }
-
-  private void btnExecute_Click(object o, EventArgs args)
-  {
-    var executor = new TestExecutor(mContainer, mEntryCrownTestsExe.Text);
-    executor.ExecuteAll();
-    RefreshData();
-  }
-
-  private void LoadConfigData()
-  {
-    mEntryTestFolder.Text = @"..\..\..\..\..\tests\";
-    mEntryCrownTestsExe.Text = @"..\..\..\..\..\build\tests\Debug\crown-tests.exe";
-  }
-
-  private void LoadTestsData()
-  {
-    var testsJsonFullfileName = System.IO.Path.Combine(mEntryTestFolder.Text, "tests.json");
-    mContainer = JsonConvert.DeserializeObject<TestContainer>(System.IO.File.ReadAllText(testsJsonFullfileName));
-
-    RefreshData();
-  }
-
-  private void RefreshData()
-  {
-    mTreeStore = new Gtk.TreeStore(typeof(string), typeof(string));
-    //mTreeStore.Clear();
-    foreach (var category in mContainer.Categories)
-    {
-      var iter = mTreeStore.AppendValues(category.Name);
-      foreach (var test in category.Tests)
-      {
-        mTreeStore.AppendValues(iter, test.Name, test.LastResult == 0 ? "Passed" : "Failed");
-      }
-    }
-
-    mTreeView.Model = mTreeStore;
-  }
-
-  protected override bool OnDeleteEvent(Gdk.Event evnt)
-  {
-    Application.Quit();
-    return base.OnDeleteEvent(evnt);
-  }
-}
+	public partial class MainWindow : Gtk.Window
+	{
+		private TestContainer mContainer;
+
+		public MainWindow() : 
+			base(Gtk.WindowType.Toplevel)
+		{
+			this.Build();
+
+			//Set the ViewModels for each widget
+			var crownTestsViewModel = new CrownTestsViewModel();
+			BindingEngine.SetViewModel(this, crownTestsViewModel);
+			//txtTestFolder and txtCrownTestsExe automatically inherit the view model of the MainWindow because it has not been not specified
+
+			//Create and Apply templates for each widget
+			Templating.ApplyTemplate(twTests,
+				new TreeViewTemplate()
+					.AddColumn("Name", new Gtk.CellRendererText())
+					.AddColumn("State", new Gtk.CellRendererText())
+				  .AddRowTemplate(TreeViewRowTemplate.Create(typeof(TestCategory))
+																						 .SetBinding("Name", "Name"))
+					.AddRowTemplate(TreeViewRowTemplate.Create(typeof(Test))
+																						 .SetBinding("Name", "Name")
+																						 .SetBinding("State", "LastResult")));
+
+			Templating.ApplyTemplate(txtTestFolder,
+				new EntryTemplate().SetTextBinding("TestFolder"));
+
+			Templating.ApplyTemplate(txtCrownTestsExe,
+				new EntryTemplate().SetTextBinding("CrownTestsExe"));
+
+			LoadTestsData();
+		}
+
+		protected void OnDeleteEvent(object sender, DeleteEventArgs a)
+		{
+			Application.Quit();
+			a.RetVal = true;
+		}
+
+		#region "My Code"
+
+		private void btnCreate_Click(object o, EventArgs args)
+		{
+			var creator = new TestSourceCreator(mContainer, txtTestFolder.Text);
+			creator.Create();
+		}
+
+		private void btnExecute_Click(object o, EventArgs args)
+		{
+			var executor = new TestExecutor(mContainer, txtCrownTestsExe.Text);
+			executor.ExecuteAll();
+		}
+
+		private void LoadTestsData()
+		{
+			var testsJsonFullfileName = System.IO.Path.Combine(txtTestFolder.Text, "tests.json");
+			if (!System.IO.File.Exists(testsJsonFullfileName)) {
+				Console.WriteLine("Could not find test data: " + testsJsonFullfileName);
+				return;
+			}
+			mContainer = JsonConvert.DeserializeObject<TestContainer>(System.IO.File.ReadAllText(testsJsonFullfileName));
+
+			RefreshData();
+		}
+
+		private void RefreshData()
+		{
+			var treeStore = twTests.Model as Gtk.TreeStore;
+			if (treeStore == null)
+				treeStore = new Gtk.TreeStore(typeof(object));
+			treeStore.Clear();
+			foreach (var category in mContainer.Categories) {
+				var iter = treeStore.AppendValues(category);
+				foreach (var test in category.Tests) {
+					treeStore.AppendValues(iter, test);
+				}
+			}
+
+			twTests.Model = treeStore;
+		}
+
+		#endregion
+	}
+}
+

+ 10 - 10
tools/gui/crown-tests/Program.cs

@@ -4,14 +4,14 @@ using Glade;
 
 namespace crown_tests
 {
-  public class Program
-  {
-    public static void Main(string[] args)
-    {
-      Application.Init();
-      MainWindow win = new MainWindow();
-      win.ShowAll();
-      Application.Run();
-    }
-  }
+	public class Program
+	{
+		public static void Main(string[] args)
+		{
+			Application.Init();
+			var win = new MainWindow();
+			win.ShowAll();
+			Application.Run();
+		}
+	}
 }

+ 27 - 0
tools/gui/crown-tests/ViewModels/CrownTestsViewModel.cs

@@ -0,0 +1,27 @@
+using System;
+using crown_tests.GtkExt;
+
+namespace crown_tests.ViewModels
+{
+	public class CrownTestsViewModel: ViewModelBase
+	{
+		public CrownTestsViewModel()
+		{
+			TestFolder = @"..\..\..\..\..\tests\";
+			CrownTestsExe = @"..\..\..\..\..\build\tests\Debug\crown-tests.exe";
+		}
+
+		private String mTestFolder;
+		public String TestFolder {
+			get { return mTestFolder; }
+			set { SetAndNotify(ref mTestFolder, value, "TestFolder"); }
+		}
+
+		private String mCrownTestsExe;
+		public String CrownTestsExe {
+			get { return mCrownTestsExe; }
+			set { SetAndNotify(ref mCrownTestsExe, value, "CrownTestsExe"); }
+		}
+	}
+}
+

+ 25 - 1
tools/gui/crown-tests/crown-tests.csproj

@@ -12,6 +12,8 @@
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <TargetFrameworkProfile />
+    <ProductVersion>12.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>x86</PlatformTarget>
@@ -58,7 +60,6 @@
     <Reference Include="Mono.Posix" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="MainWindow.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="tests\Test.cs" />
@@ -66,6 +67,19 @@
     <Compile Include="tests\TestContainer.cs" />
     <Compile Include="tests\TestExecutor.cs" />
     <Compile Include="tests\TestSourceCreator.cs" />
+    <Compile Include="gtk-gui\generated.cs" />
+    <Compile Include="gtk-gui\crown_tests.MainWindow.cs" />
+    <Compile Include="MainWindow.cs" />
+    <Compile Include="GtkExt\TreeViewRowTemplate.cs" />
+    <Compile Include="GtkExt\IPropertyChanged.cs" />
+    <Compile Include="GtkExt\ViewModelBase.cs" />
+    <Compile Include="GtkExt\BindingEngine.cs" />
+    <Compile Include="GtkExt\BindingTargets\TreeViewIterBindingTarget.cs" />
+    <Compile Include="GtkExt\BindingTargets\EntryBindingTarget.cs" />
+    <Compile Include="GtkExt\Templating.cs" />
+    <Compile Include="GtkExt\TreeViewTemplate.cs" />
+    <Compile Include="GtkExt\EntryTemplate.cs" />
+    <Compile Include="ViewModels\CrownTestsViewModel.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
@@ -75,4 +89,14 @@
   <Target Name="AfterBuild">
   </Target>
   -->
+  <ItemGroup>
+    <EmbeddedResource Include="gtk-gui\gui.stetic">
+      <LogicalName>gui.stetic</LogicalName>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="GtkExt\" />
+    <Folder Include="GtkExt\BindingTargets\" />
+    <Folder Include="ViewModels\" />
+  </ItemGroup>
 </Project>

+ 170 - 0
tools/gui/crown-tests/gtk-gui/crown_tests.MainWindow.cs

@@ -0,0 +1,170 @@
+
+// This file has been generated by the GUI designer. Do not modify.
+namespace crown_tests
+{
+	public partial class MainWindow
+	{
+		private global::Gtk.VBox vbox1;
+		private global::Gtk.Table table1;
+		private global::Gtk.Label label1;
+		private global::Gtk.Label label2;
+		private global::Gtk.Entry txtCrownTestsExe;
+		private global::Gtk.Entry txtTestFolder;
+		private global::Gtk.HBox hbox2;
+		private global::Gtk.ScrolledWindow GtkScrolledWindow;
+		private global::Gtk.TreeView twTests;
+		private global::Gtk.Alignment alignment1;
+		private global::Gtk.Frame frame1;
+		private global::Gtk.Alignment GtkAlignment;
+		private global::Gtk.VBox vbox3;
+		private global::Gtk.Button btnCreate;
+		private global::Gtk.Button btnExecute;
+		private global::Gtk.Label GtkLabel1;
+
+		protected virtual void Build ()
+		{
+			global::Stetic.Gui.Initialize (this);
+			// Widget crown_tests.MainWindow
+			this.Name = "crown_tests.MainWindow";
+			this.Title = global::Mono.Unix.Catalog.GetString ("Test Browser");
+			this.WindowPosition = ((global::Gtk.WindowPosition)(4));
+			this.DefaultWidth = 500;
+			this.DefaultHeight = 350;
+			// Container child crown_tests.MainWindow.Gtk.Container+ContainerChild
+			this.vbox1 = new global::Gtk.VBox ();
+			this.vbox1.Name = "vbox1";
+			this.vbox1.Spacing = 6;
+			this.vbox1.BorderWidth = ((uint)(6));
+			// Container child vbox1.Gtk.Box+BoxChild
+			this.table1 = new global::Gtk.Table (((uint)(2)), ((uint)(2)), false);
+			this.table1.Name = "table1";
+			this.table1.RowSpacing = ((uint)(6));
+			this.table1.ColumnSpacing = ((uint)(6));
+			// Container child table1.Gtk.Table+TableChild
+			this.label1 = new global::Gtk.Label ();
+			this.label1.Name = "label1";
+			this.label1.LabelProp = global::Mono.Unix.Catalog.GetString ("Tests folder");
+			this.table1.Add (this.label1);
+			global::Gtk.Table.TableChild w1 = ((global::Gtk.Table.TableChild)(this.table1 [this.label1]));
+			w1.XOptions = ((global::Gtk.AttachOptions)(4));
+			w1.YOptions = ((global::Gtk.AttachOptions)(4));
+			// Container child table1.Gtk.Table+TableChild
+			this.label2 = new global::Gtk.Label ();
+			this.label2.Name = "label2";
+			this.label2.LabelProp = global::Mono.Unix.Catalog.GetString ("crown-tests executable");
+			this.table1.Add (this.label2);
+			global::Gtk.Table.TableChild w2 = ((global::Gtk.Table.TableChild)(this.table1 [this.label2]));
+			w2.TopAttach = ((uint)(1));
+			w2.BottomAttach = ((uint)(2));
+			w2.XOptions = ((global::Gtk.AttachOptions)(4));
+			w2.YOptions = ((global::Gtk.AttachOptions)(4));
+			// Container child table1.Gtk.Table+TableChild
+			this.txtCrownTestsExe = new global::Gtk.Entry ();
+			this.txtCrownTestsExe.CanFocus = true;
+			this.txtCrownTestsExe.Name = "txtCrownTestsExe";
+			this.txtCrownTestsExe.IsEditable = true;
+			this.txtCrownTestsExe.InvisibleChar = '●';
+			this.table1.Add (this.txtCrownTestsExe);
+			global::Gtk.Table.TableChild w3 = ((global::Gtk.Table.TableChild)(this.table1 [this.txtCrownTestsExe]));
+			w3.TopAttach = ((uint)(1));
+			w3.BottomAttach = ((uint)(2));
+			w3.LeftAttach = ((uint)(1));
+			w3.RightAttach = ((uint)(2));
+			w3.YOptions = ((global::Gtk.AttachOptions)(4));
+			// Container child table1.Gtk.Table+TableChild
+			this.txtTestFolder = new global::Gtk.Entry ();
+			this.txtTestFolder.CanFocus = true;
+			this.txtTestFolder.Name = "txtTestFolder";
+			this.txtTestFolder.IsEditable = true;
+			this.txtTestFolder.InvisibleChar = '●';
+			this.table1.Add (this.txtTestFolder);
+			global::Gtk.Table.TableChild w4 = ((global::Gtk.Table.TableChild)(this.table1 [this.txtTestFolder]));
+			w4.LeftAttach = ((uint)(1));
+			w4.RightAttach = ((uint)(2));
+			w4.YOptions = ((global::Gtk.AttachOptions)(4));
+			this.vbox1.Add (this.table1);
+			global::Gtk.Box.BoxChild w5 = ((global::Gtk.Box.BoxChild)(this.vbox1 [this.table1]));
+			w5.Position = 0;
+			w5.Expand = false;
+			w5.Fill = false;
+			// Container child vbox1.Gtk.Box+BoxChild
+			this.hbox2 = new global::Gtk.HBox ();
+			this.hbox2.Name = "hbox2";
+			this.hbox2.Spacing = 6;
+			// Container child hbox2.Gtk.Box+BoxChild
+			this.GtkScrolledWindow = new global::Gtk.ScrolledWindow ();
+			this.GtkScrolledWindow.Name = "GtkScrolledWindow";
+			this.GtkScrolledWindow.ShadowType = ((global::Gtk.ShadowType)(1));
+			// Container child GtkScrolledWindow.Gtk.Container+ContainerChild
+			this.twTests = new global::Gtk.TreeView ();
+			this.twTests.CanFocus = true;
+			this.twTests.Name = "twTests";
+			this.GtkScrolledWindow.Add (this.twTests);
+			this.hbox2.Add (this.GtkScrolledWindow);
+			global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.hbox2 [this.GtkScrolledWindow]));
+			w7.Position = 0;
+			// Container child hbox2.Gtk.Box+BoxChild
+			this.alignment1 = new global::Gtk.Alignment (0F, 0F, 1F, 1F);
+			this.alignment1.Name = "alignment1";
+			// Container child alignment1.Gtk.Container+ContainerChild
+			this.frame1 = new global::Gtk.Frame ();
+			this.frame1.WidthRequest = 120;
+			this.frame1.Name = "frame1";
+			this.frame1.ShadowType = ((global::Gtk.ShadowType)(0));
+			// Container child frame1.Gtk.Container+ContainerChild
+			this.GtkAlignment = new global::Gtk.Alignment (0F, 0F, 1F, 1F);
+			this.GtkAlignment.Name = "GtkAlignment";
+			this.GtkAlignment.LeftPadding = ((uint)(12));
+			// Container child GtkAlignment.Gtk.Container+ContainerChild
+			this.vbox3 = new global::Gtk.VBox ();
+			this.vbox3.Name = "vbox3";
+			this.vbox3.Spacing = 6;
+			// Container child vbox3.Gtk.Box+BoxChild
+			this.btnCreate = new global::Gtk.Button ();
+			this.btnCreate.CanFocus = true;
+			this.btnCreate.Name = "btnCreate";
+			this.btnCreate.UseUnderline = true;
+			this.btnCreate.Label = global::Mono.Unix.Catalog.GetString ("Create");
+			this.vbox3.Add (this.btnCreate);
+			global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.btnCreate]));
+			w8.Position = 0;
+			w8.Expand = false;
+			w8.Fill = false;
+			// Container child vbox3.Gtk.Box+BoxChild
+			this.btnExecute = new global::Gtk.Button ();
+			this.btnExecute.CanFocus = true;
+			this.btnExecute.Name = "btnExecute";
+			this.btnExecute.UseUnderline = true;
+			this.btnExecute.Label = global::Mono.Unix.Catalog.GetString ("Execute");
+			this.vbox3.Add (this.btnExecute);
+			global::Gtk.Box.BoxChild w9 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.btnExecute]));
+			w9.Position = 1;
+			w9.Expand = false;
+			w9.Fill = false;
+			this.GtkAlignment.Add (this.vbox3);
+			this.frame1.Add (this.GtkAlignment);
+			this.GtkLabel1 = new global::Gtk.Label ();
+			this.GtkLabel1.Name = "GtkLabel1";
+			this.GtkLabel1.LabelProp = global::Mono.Unix.Catalog.GetString ("Operations");
+			this.GtkLabel1.UseMarkup = true;
+			this.frame1.LabelWidget = this.GtkLabel1;
+			this.alignment1.Add (this.frame1);
+			this.hbox2.Add (this.alignment1);
+			global::Gtk.Box.BoxChild w13 = ((global::Gtk.Box.BoxChild)(this.hbox2 [this.alignment1]));
+			w13.Position = 1;
+			w13.Expand = false;
+			w13.Fill = false;
+			this.vbox1.Add (this.hbox2);
+			global::Gtk.Box.BoxChild w14 = ((global::Gtk.Box.BoxChild)(this.vbox1 [this.hbox2]));
+			w14.Position = 1;
+			this.Add (this.vbox1);
+			if ((this.Child != null)) {
+				this.Child.ShowAll ();
+			}
+			this.Show ();
+			this.DeleteEvent += new global::Gtk.DeleteEventHandler (this.OnDeleteEvent);
+			this.btnCreate.Clicked += new global::System.EventHandler (this.btnCreate_Click);
+			this.btnExecute.Clicked += new global::System.EventHandler (this.btnExecute_Click);
+		}
+	}
+}

+ 29 - 0
tools/gui/crown-tests/gtk-gui/generated.cs

@@ -0,0 +1,29 @@
+
+// This file has been generated by the GUI designer. Do not modify.
+namespace Stetic
+{
+	internal class Gui
+	{
+		private static bool initialized;
+
+		internal static void Initialize (Gtk.Widget iconRenderer)
+		{
+			if ((Stetic.Gui.initialized == false)) {
+				Stetic.Gui.initialized = true;
+			}
+		}
+	}
+
+	internal class ActionGroups
+	{
+		public static Gtk.ActionGroup GetActionGroup (System.Type type)
+		{
+			return Stetic.ActionGroups.GetActionGroup (type.FullName);
+		}
+
+		public static Gtk.ActionGroup GetActionGroup (string name)
+		{
+			return null;
+		}
+	}
+}

+ 221 - 0
tools/gui/crown-tests/gtk-gui/gui.stetic

@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="utf-8"?>
+<stetic-interface>
+  <configuration>
+    <images-root-path>..</images-root-path>
+  </configuration>
+  <import>
+    <widget-library name="glade-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <widget-library name="../bin/Debug/crown-tests.exe" internal="true" />
+  </import>
+  <widget class="Gtk.Window" id="crown_tests.MainWindow" design-size="400 300">
+    <property name="MemberName" />
+    <property name="Title" translatable="yes">Test Browser</property>
+    <property name="WindowPosition">CenterOnParent</property>
+    <property name="DefaultWidth">500</property>
+    <property name="DefaultHeight">350</property>
+    <signal name="DeleteEvent" handler="OnDeleteEvent" />
+    <child>
+      <widget class="Gtk.VBox" id="vbox1">
+        <property name="MemberName" />
+        <property name="Spacing">6</property>
+        <property name="BorderWidth">6</property>
+        <child>
+          <widget class="Gtk.Table" id="table1">
+            <property name="MemberName" />
+            <property name="NRows">2</property>
+            <property name="NColumns">2</property>
+            <property name="RowSpacing">6</property>
+            <property name="ColumnSpacing">6</property>
+            <child>
+              <widget class="Gtk.Label" id="label1">
+                <property name="MemberName" />
+                <property name="LabelProp" translatable="yes">Tests folder</property>
+              </widget>
+              <packing>
+                <property name="AutoSize">True</property>
+                <property name="XOptions">Fill</property>
+                <property name="YOptions">Fill</property>
+                <property name="XExpand">False</property>
+                <property name="XFill">True</property>
+                <property name="XShrink">False</property>
+                <property name="YExpand">False</property>
+                <property name="YFill">True</property>
+                <property name="YShrink">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="Gtk.Label" id="label2">
+                <property name="MemberName" />
+                <property name="LabelProp" translatable="yes">crown-tests executable</property>
+              </widget>
+              <packing>
+                <property name="TopAttach">1</property>
+                <property name="BottomAttach">2</property>
+                <property name="AutoSize">False</property>
+                <property name="XOptions">Fill</property>
+                <property name="YOptions">Fill</property>
+                <property name="XExpand">False</property>
+                <property name="XFill">True</property>
+                <property name="XShrink">False</property>
+                <property name="YExpand">False</property>
+                <property name="YFill">True</property>
+                <property name="YShrink">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="Gtk.Entry" id="txtCrownTestsExe">
+                <property name="MemberName" />
+                <property name="CanFocus">True</property>
+                <property name="IsEditable">True</property>
+                <property name="InvisibleChar">●</property>
+              </widget>
+              <packing>
+                <property name="TopAttach">1</property>
+                <property name="BottomAttach">2</property>
+                <property name="LeftAttach">1</property>
+                <property name="RightAttach">2</property>
+                <property name="AutoSize">True</property>
+                <property name="YOptions">Fill</property>
+                <property name="XExpand">True</property>
+                <property name="XFill">True</property>
+                <property name="XShrink">False</property>
+                <property name="YExpand">False</property>
+                <property name="YFill">True</property>
+                <property name="YShrink">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="Gtk.Entry" id="txtTestFolder">
+                <property name="MemberName" />
+                <property name="CanFocus">True</property>
+                <property name="IsEditable">True</property>
+                <property name="InvisibleChar">●</property>
+              </widget>
+              <packing>
+                <property name="LeftAttach">1</property>
+                <property name="RightAttach">2</property>
+                <property name="AutoSize">True</property>
+                <property name="YOptions">Fill</property>
+                <property name="XExpand">True</property>
+                <property name="XFill">True</property>
+                <property name="XShrink">False</property>
+                <property name="YExpand">False</property>
+                <property name="YFill">True</property>
+                <property name="YShrink">False</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="Position">0</property>
+            <property name="AutoSize">True</property>
+            <property name="Expand">False</property>
+            <property name="Fill">False</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="Gtk.HBox" id="hbox2">
+            <property name="MemberName" />
+            <property name="Spacing">6</property>
+            <child>
+              <widget class="Gtk.ScrolledWindow" id="GtkScrolledWindow">
+                <property name="MemberName" />
+                <property name="ShadowType">In</property>
+                <child>
+                  <widget class="Gtk.TreeView" id="twTests">
+                    <property name="MemberName" />
+                    <property name="CanFocus">True</property>
+                    <property name="ShowScrollbars">True</property>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="Position">0</property>
+                <property name="AutoSize">True</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="Gtk.Alignment" id="alignment1">
+                <property name="MemberName" />
+                <property name="Xalign">0</property>
+                <property name="Yalign">0</property>
+                <child>
+                  <widget class="Gtk.Frame" id="frame1">
+                    <property name="MemberName" />
+                    <property name="WidthRequest">120</property>
+                    <property name="ShadowType">None</property>
+                    <child>
+                      <widget class="Gtk.Alignment" id="GtkAlignment">
+                        <property name="MemberName" />
+                        <property name="Xalign">0</property>
+                        <property name="Yalign">0</property>
+                        <property name="LeftPadding">12</property>
+                        <child>
+                          <widget class="Gtk.VBox" id="vbox3">
+                            <property name="MemberName" />
+                            <property name="Spacing">6</property>
+                            <child>
+                              <widget class="Gtk.Button" id="btnCreate">
+                                <property name="MemberName" />
+                                <property name="CanFocus">True</property>
+                                <property name="Type">TextOnly</property>
+                                <property name="Label" translatable="yes">Create</property>
+                                <property name="UseUnderline">True</property>
+                                <signal name="Clicked" handler="btnCreate_Click" />
+                              </widget>
+                              <packing>
+                                <property name="Position">0</property>
+                                <property name="AutoSize">True</property>
+                                <property name="Expand">False</property>
+                                <property name="Fill">False</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="Gtk.Button" id="btnExecute">
+                                <property name="MemberName" />
+                                <property name="CanFocus">True</property>
+                                <property name="Type">TextOnly</property>
+                                <property name="Label" translatable="yes">Execute</property>
+                                <property name="UseUnderline">True</property>
+                                <signal name="Clicked" handler="btnExecute_Click" />
+                              </widget>
+                              <packing>
+                                <property name="Position">1</property>
+                                <property name="AutoSize">True</property>
+                                <property name="Expand">False</property>
+                                <property name="Fill">False</property>
+                              </packing>
+                            </child>
+                          </widget>
+                        </child>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="Gtk.Label" id="GtkLabel1">
+                        <property name="MemberName" />
+                        <property name="LabelProp" translatable="yes">Operations</property>
+                        <property name="UseMarkup">True</property>
+                      </widget>
+                      <packing>
+                        <property name="type">label_item</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="Position">1</property>
+                <property name="AutoSize">True</property>
+                <property name="Expand">False</property>
+                <property name="Fill">False</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="Position">1</property>
+            <property name="AutoSize">False</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</stetic-interface>

+ 30 - 13
tools/gui/crown-tests/tests/Test.cs

@@ -3,22 +3,39 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using crown_tests.GtkExt;
 
 namespace crown_tests.tests
 {
-  [JsonObject(MemberSerialization.OptIn)]
-  public class Test
-  {
-    [JsonProperty]
-    public String Name;
-    [JsonProperty]
-    public String Description;
+	public enum ETestResult {
+		Unknown = 0,
+		Failed = 1,
+		Passed = 2
+	}
 
-    public int LastResult;
+	[JsonObject(MemberSerialization.OptIn)]
+	public class Test: ViewModelBase
+	{
+		[JsonProperty]
+		public String Name { get; set; }
 
-    public String GetFunctionName()
-    {
-      return "test_" + Name.ToLower().Replace(' ', '_');
-    }
-  }
+		[JsonProperty]
+		public String Description { get; set; }
+
+		ETestResult mLastResult;
+		public ETestResult LastResult { 
+			get { return mLastResult; }
+			set {
+				if (mLastResult != value) {
+					mLastResult = value;
+					Notify("LastResult");
+				}
+			}
+		}
+
+		public String GetFunctionName()
+		{
+			return "test_" + Name.ToLower().Replace(' ', '_');
+		}
+	}
 }

+ 17 - 16
tools/gui/crown-tests/tests/TestCategory.cs

@@ -6,22 +6,23 @@ using System.Text;
 
 namespace crown_tests.tests
 {
-  [JsonObject(MemberSerialization.OptIn)]
-  public class TestCategory
-  {
-    [JsonProperty]
-    public String Name;
-    [JsonProperty]
-    public String Description;
-    [JsonProperty]
-    public List<Test> Tests;
+	[JsonObject(MemberSerialization.OptIn)]
+	public class TestCategory
+	{
+		[JsonProperty]
+		public String Name { get; set; }
 
-    public TestCategory(String name, String description)
-    {
-      Tests = new List<Test>();
-      Name = name;
-      Description = description;
-    }
+		[JsonProperty]
+		public String Description { get; set; }
 
-  }
+		[JsonProperty]
+		public List<Test> Tests { get; set; }
+
+		public TestCategory(String name, String description)
+		{
+			Tests = new List<Test>();
+			Name = name;
+			Description = description;
+		}
+	}
 }

+ 10 - 10
tools/gui/crown-tests/tests/TestContainer.cs

@@ -6,15 +6,15 @@ using System.Text;
 
 namespace crown_tests.tests
 {
-  [JsonObject(MemberSerialization.OptIn)]
-  public class TestContainer
-  {
-    [JsonProperty]
-    public List<TestCategory> Categories;
+	[JsonObject(MemberSerialization.OptIn)]
+	public class TestContainer
+	{
+		[JsonProperty]
+		public List<TestCategory> Categories { get; set; }
 
-    public TestContainer()
-    {
-      Categories = new List<TestCategory>();
-    }
-  }
+		public TestContainer()
+		{
+			Categories = new List<TestCategory>();
+		}
+	}
 }

+ 24 - 26
tools/gui/crown-tests/tests/TestExecutor.cs

@@ -6,32 +6,30 @@ using System.Text;
 
 namespace crown_tests.tests
 {
-  public class TestExecutor
-  {
-    private TestContainer mContainer;
-    private String mExeFullFileName;
+	public class TestExecutor
+	{
+		private TestContainer mContainer;
+		private String mExeFullFileName;
 
-    public TestExecutor(TestContainer container, String exeFullFileName)
-    {
-      mContainer = container;
-      mExeFullFileName = exeFullFileName;
-    }
+		public TestExecutor(TestContainer container, String exeFullFileName)
+		{
+			mContainer = container;
+			mExeFullFileName = exeFullFileName;
+		}
 
-    public void ExecuteAll()
-    {
-      foreach (var category in mContainer.Categories)
-      {
-        foreach (var test in category.Tests)
-        {
-          var p = new Process();
-          p.StartInfo.FileName = mExeFullFileName;
-          p.StartInfo.Arguments = string.Format("/test:\"{0}\"", test.Name);
-          p.Start();
-          p.WaitForExit();
-          test.LastResult = p.ExitCode;
-          System.Threading.Thread.Sleep(1500);
-        }
-      }
-    }
-  }
+		public void ExecuteAll()
+		{
+			foreach (var category in mContainer.Categories) {
+				foreach (var test in category.Tests) {
+					var p = new Process();
+					p.StartInfo.FileName = mExeFullFileName;
+					p.StartInfo.Arguments = string.Format("/test:\"{0}\"", test.Name);
+					p.Start();
+					p.WaitForExit();
+					test.LastResult = (p.ExitCode == 0) ? ETestResult.Passed : ETestResult.Failed;
+					System.Threading.Thread.Sleep(1500);
+				}
+			}
+		}
+	}
 }

+ 74 - 80
tools/gui/crown-tests/tests/TestSourceCreator.cs

@@ -6,97 +6,91 @@ using System.IO;
 
 namespace crown_tests.tests
 {
-  public class TestSourceCreator
-  {
-    private TestContainer mContainer;
-    private String mDestFolder;
+	public class TestSourceCreator
+	{
+		private TestContainer mContainer;
+		private String mDestFolder;
 
-    public TestSourceCreator(TestContainer container, String destfolder)
-    {
-      mContainer = container;
-      mDestFolder = destfolder;
-    }
+		public TestSourceCreator(TestContainer container, String destfolder)
+		{
+			mContainer = container;
+			mDestFolder = destfolder;
+		}
 
-    public void Create()
-    {
-      if (!Directory.Exists(mDestFolder))
-        Directory.CreateDirectory(mDestFolder);
+		public void Create()
+		{
+			if (!Directory.Exists(mDestFolder))
+				Directory.CreateDirectory(mDestFolder);
 
-      CreateMainFile();
+			CreateMainFile();
 
-      foreach (var category in mContainer.Categories)
-      {
-        String folder = Path.Combine(mDestFolder, category.Name);
-        if (!Directory.Exists(folder))
-          Directory.CreateDirectory(folder);
-        foreach (var test in category.Tests)
-        {
-          MaybeCreateTestFile(folder, test);
-        }
-      }
-    }
+			foreach (var category in mContainer.Categories) {
+				String folder = Path.Combine(mDestFolder, category.Name);
+				if (!Directory.Exists(folder))
+					Directory.CreateDirectory(folder);
+				foreach (var test in category.Tests) {
+					MaybeCreateTestFile(folder, test);
+				}
+			}
+		}
 
-    private void CreateMainFile()
-    {
-      var mainFullFileName = Path.Combine(mDestFolder, "main.cpp");
+		private void CreateMainFile()
+		{
+			var mainFullFileName = Path.Combine(mDestFolder, "main.cpp");
 
-      String content = "";
+			String content = "";
 
-      foreach (var category in mContainer.Categories)
-      {
-        content += "//Category '" + category.Name + "'" + Environment.NewLine;
-        foreach (var test in category.Tests)
-        {
-          content += "#include \"" + test.Name + ".h\"" + Environment.NewLine;
-        }
-      }
+			foreach (var category in mContainer.Categories) {
+				content += "//Category '" + category.Name + "'" + Environment.NewLine;
+				foreach (var test in category.Tests) {
+					content += "#include \"" + test.Name + ".h\"" + Environment.NewLine;
+				}
+			}
 
-      content += Environment.NewLine;
-      content += "#include <string.h>" + Environment.NewLine;
-      content += "int main(int argc, char** argv)" + Environment.NewLine;
-      content += "{" + Environment.NewLine;
-      content += "  if (argc < 2) return -1;" + Environment.NewLine;
-      foreach (var category in mContainer.Categories)
-      {
-        foreach (var test in category.Tests)
-        {
-          content += "  if (strcmp(argv[1], \"/test:" + test.Name + "\") == 0)" + Environment.NewLine;
-          content += "  return " + test.GetFunctionName() + "();" + Environment.NewLine;
-        }
-      }
-      content += "  return -2;" + Environment.NewLine;
-      content += "}" + Environment.NewLine;
-      File.WriteAllText(mainFullFileName, content);
-    }
+			content += Environment.NewLine;
+			content += "#include <string.h>" + Environment.NewLine;
+			content += "int main(int argc, char** argv)" + Environment.NewLine;
+			content += "{" + Environment.NewLine;
+			content += "  if (argc < 2) return -1;" + Environment.NewLine;
+			foreach (var category in mContainer.Categories) {
+				foreach (var test in category.Tests) {
+					content += "  if (strcmp(argv[1], \"/test:" + test.Name + "\") == 0)" + Environment.NewLine;
+					content += "  return " + test.GetFunctionName() + "();" + Environment.NewLine;
+				}
+			}
+			content += "  return -2;" + Environment.NewLine;
+			content += "}" + Environment.NewLine;
+			File.WriteAllText(mainFullFileName, content);
+		}
 
-    private void MaybeCreateTestFile(String fullFolderName, Test test)
-    {
-      var headerFullFileName = Path.Combine(fullFolderName, test.Name + ".h");
-      var sourceFullFileName = Path.Combine(fullFolderName, test.Name + ".cpp");
+		private void MaybeCreateTestFile(String fullFolderName, Test test)
+		{
+			var headerFullFileName = Path.Combine(fullFolderName, test.Name + ".h");
+			var sourceFullFileName = Path.Combine(fullFolderName, test.Name + ".cpp");
 
-      if (File.Exists(headerFullFileName) || File.Exists(sourceFullFileName))
-        return;
+			if (File.Exists(headerFullFileName) || File.Exists(sourceFullFileName))
+				return;
 
-      //Header
-      String headerContent = "";
-      headerContent += "#pragma once" + Environment.NewLine;
-      headerContent += Environment.NewLine;
-      headerContent += "int " + test.GetFunctionName() + "();" + Environment.NewLine;
-      File.WriteAllText(headerFullFileName, headerContent);
+			//Header
+			String headerContent = "";
+			headerContent += "#pragma once" + Environment.NewLine;
+			headerContent += Environment.NewLine;
+			headerContent += "int " + test.GetFunctionName() + "();" + Environment.NewLine;
+			File.WriteAllText(headerFullFileName, headerContent);
 
-      //Source
-      String sourceContent = "";
-      sourceContent += "#include \"" + Path.GetFileName(headerFullFileName) + "\"" + Environment.NewLine;
-      sourceContent += "#include <time.h>" + Environment.NewLine;
-      sourceContent += "#include <stdlib.h>" + Environment.NewLine;
-      sourceContent += Environment.NewLine;
-      sourceContent += "int " + test.GetFunctionName() + "()" + Environment.NewLine;
-      sourceContent += "{" + Environment.NewLine;
-      sourceContent += "  srand(time(NULL));" + Environment.NewLine;
-      sourceContent += "  return rand() % 2;" + Environment.NewLine;
-      sourceContent += "}" + Environment.NewLine;
-      File.WriteAllText(sourceFullFileName, sourceContent);
-    }
-  }
+			//Source
+			String sourceContent = "";
+			sourceContent += "#include \"" + Path.GetFileName(headerFullFileName) + "\"" + Environment.NewLine;
+			sourceContent += "#include <time.h>" + Environment.NewLine;
+			sourceContent += "#include <stdlib.h>" + Environment.NewLine;
+			sourceContent += Environment.NewLine;
+			sourceContent += "int " + test.GetFunctionName() + "()" + Environment.NewLine;
+			sourceContent += "{" + Environment.NewLine;
+			sourceContent += "  srand(time(NULL));" + Environment.NewLine;
+			sourceContent += "  return rand() % 2;" + Environment.NewLine;
+			sourceContent += "}" + Environment.NewLine;
+			File.WriteAllText(sourceFullFileName, sourceContent);
+		}
+	}
 }
 

+ 36 - 13
tools/gui/toolbox.sln

@@ -17,16 +17,6 @@ Global
 		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|Any CPU.ActiveCfg = Debug|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|Mixed Platforms.Build.0 = Debug|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|x86.ActiveCfg = Debug|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|x86.Build.0 = Debug|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|Any CPU.ActiveCfg = Release|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|Mixed Platforms.ActiveCfg = Release|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|Mixed Platforms.Build.0 = Release|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|x86.ActiveCfg = Release|x86
-		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|x86.Build.0 = Release|x86
 		{1307AAA0-BFB8-494C-83CB-A4171C2E87F1}.Debug|Any CPU.ActiveCfg = Debug|x86
 		{1307AAA0-BFB8-494C-83CB-A4171C2E87F1}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
 		{1307AAA0-BFB8-494C-83CB-A4171C2E87F1}.Debug|Mixed Platforms.Build.0 = Debug|x86
@@ -47,11 +37,44 @@ Global
 		{4E5F6C4E-8C96-4903-B6AE-5C9ADCFC2C20}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
 		{4E5F6C4E-8C96-4903-B6AE-5C9ADCFC2C20}.Release|Mixed Platforms.Build.0 = Release|Any CPU
 		{4E5F6C4E-8C96-4903-B6AE-5C9ADCFC2C20}.Release|x86.ActiveCfg = Release|Any CPU
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|Mixed Platforms.Build.0 = Debug|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|x86.ActiveCfg = Debug|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Debug|x86.Build.0 = Debug|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|Any CPU.ActiveCfg = Release|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|Mixed Platforms.ActiveCfg = Release|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|Mixed Platforms.Build.0 = Release|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|x86.ActiveCfg = Release|x86
+		{7FF1E014-7560-481E-B919-080C42F4C6EC}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(MonoDevelopProperties) = preSolution
+		StartupItem = crown-tests\crown-tests.csproj
+		Policies = $0
+		$0.TextStylePolicy = $1
+		$1.inheritsSet = null
+		$1.scope = text/x-csharp
+		$0.CSharpFormattingPolicy = $2
+		$2.BeforeMethodDeclarationParentheses = False
+		$2.BeforeMethodCallParentheses = False
+		$2.BeforeConstructorDeclarationParentheses = False
+		$2.BeforeIndexerDeclarationBracket = False
+		$2.BeforeDelegateDeclarationParentheses = False
+		$2.AfterDelegateDeclarationParameterComma = True
+		$2.NewParentheses = False
+		$2.SpacesBeforeBrackets = False
+		$2.inheritsSet = Mono
+		$2.inheritsScope = text/x-csharp
+		$2.scope = text/x-csharp
+		$0.TextStylePolicy = $3
+		$3.FileWidth = 120
+		$3.TabWidth = 2
+		$3.IndentWidth = 2
+		$3.inheritsSet = Mono
+		$3.inheritsScope = text/plain
+		$3.scope = text/plain
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = console\console.csproj
-	EndGlobalSection
 EndGlobal