Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

twins: Collection support, handle ObservableCollection

Resolves GH-36.
  • Loading branch information...
commit 6fd2783e3e08b0452e69d2d5dcd66e83d9d75a49 1 parent 5e65dc6
@andreyvit andreyvit authored
View
241 windows/Twins/Entity.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Reflection;
+using D = System.Collections.Generic.Dictionary<string, object>;
namespace Twins
{
@@ -34,15 +36,51 @@ public static class StringExtensions
}
}
+ public interface IEntityCollectionParent
+ {
+ IEntityCollectionItem TryCreateItem(string collectionName, string itemId);
+ }
+
+ public interface IEntityCollectionItem
+ {
+ string Id { get; }
+ }
+
// A facet provides a platform-independent JSON interface to a native object.
// It turns incoming JSON payloads into native object setters/method calls,
// and sends outgoing JSON payloads when native events occur.
- public interface IFacet
+ public interface IFacet : IDisposable
{
void AddedTo(Entity entity);
void Set(Dictionary<string, object> properties);
bool TryInvoke(string name, object[] args, PayloadDelegate reply);
- bool TryResolve(string name, Dictionary<string, object> payload, out object obj);
+ bool TryResolve(string name, object payload, out object obj);
+ }
+
+ public interface IEntityOrCollection : IDisposable
+ {
+ RootEntity Root { get; }
+ string Path { get; }
+ string PathPrefix { get; }
+
+ void ProcessIncomingUpdate(object payload, PayloadDelegate reply);
+ }
+
+ public interface IChildEntityOrCollection : IEntityOrCollection
+ {
+ object NativeObject { get; }
+ }
+
+ public interface IEntity : IEntityOrCollection
+ {
+ }
+
+ public interface IChildEntity : IEntity, IChildEntityOrCollection
+ {
+ }
+
+ public interface IEntityCollection : IChildEntityOrCollection
+ {
}
// Default base class for the facets
@@ -91,7 +129,7 @@ public class Facet<NativeObj> : IFacet
}
// The default implementation tries to access a public property of this facet.
- public virtual bool TryResolve(string name, Dictionary<string, object> payload, out object resolved) {
+ public virtual bool TryResolve(string name, object payload, out object resolved) {
resolved = null;
PropertyInfo prop = GetType().GetProperty(name.ToCamelCase());
@@ -102,6 +140,9 @@ public class Facet<NativeObj> : IFacet
return false;
}
+
+ public virtual void Dispose() {
+ }
}
// Tries to access public properties, public or non-public fields and public methods of the native object.
@@ -138,7 +179,7 @@ public ReflectionFacet(Entity entity, object obj)
return false;
}
- public override bool TryResolve(string name, Dictionary<string, object> payload, out object resolved) {
+ public override bool TryResolve(string name, object payload, out object resolved) {
resolved = null;
PropertyInfo prop = obj.GetType().GetProperty(name.ToCamelCase());
@@ -157,46 +198,92 @@ public ReflectionFacet(Entity entity, object obj)
}
}
- // Entity represents a single exposed object, and holds information about its exposed children.
- public abstract class Entity
+ public abstract class EntityOrCollectionBase : IEntityOrCollection
{
- private readonly List<IFacet> facets = new List<IFacet>();
- private readonly Dictionary<string, ChildEntity> children = new Dictionary<string, ChildEntity>();
-
- public Entity() { }
+ protected readonly Dictionary<string, IChildEntityOrCollection> children = new Dictionary<string, IChildEntityOrCollection>();
public abstract RootEntity Root { get; }
public abstract string Path { get; }
public abstract string PathPrefix { get; }
- public void AddFacet(IFacet facet) {
- facets.Insert(0, facet);
- facet.AddedTo(this);
- }
+ public abstract void SendUpdate(Dictionary<string, object> payload);
- public void AddNativeObject(object obj) {
- AddFacet(new ReflectionFacet(this, obj));
+ public IChildEntityOrCollection Expose(string name, object obj) {
+ IChildEntityOrCollection entity;
+ if (!children.TryGetValue(name, out entity)) {
+ entity = CreateChildEntityOrCollection(name, obj);
+ children.Add(name, entity);
+ } else if (entity.NativeObject != obj) {
+ throw new ArgumentException("Attempting to expose different objects under the same name '" + name + "'");
+ }
+ return entity;
}
- public ChildEntity Expose(string name, object obj) {
- ChildEntity entity;
- if (!children.TryGetValue(name, out entity)) {
- entity = new ChildEntity(this, name, obj);
+ public void Unexpose(string name) {
+ IChildEntityOrCollection entity;
+ if (children.TryGetValue(name, out entity)) {
+ entity.Dispose();
+ children.Remove(name);
+ }
+ }
- foreach (FacetRegistration reg in Root.facetRegistrations) {
- IFacet facet;
- if (reg.TryCreate(entity, out facet))
- entity.AddFacet(facet);
+ private IChildEntityOrCollection CreateChildEntityOrCollection(string name, object obj) {
+ var type = obj.GetType();
+ if (type.IsGenericType) {
+ var typeDef = type.GetGenericTypeDefinition();
+ if (typeDef == typeof(ObservableCollection<>)) {
+ return CreateChildCollection(name, obj, type.GetGenericArguments()[0]);
}
+ }
+ return CreateChildEntity(name, obj);
+ }
- children.Add(name, entity);
- } else if (entity.obj != obj) {
- throw new ArgumentException("Attempting to expose different objects under the same name '" + name + "'");
+ private IChildEntity CreateChildEntity(string name, object obj) {
+ var entity = new ChildEntity(this, name, obj);
+ foreach (FacetRegistration reg in Root.facetRegistrations) {
+ IFacet facet;
+ if (reg.TryCreate(entity, out facet))
+ entity.AddFacet(facet);
}
return entity;
}
- public bool TryResolve(string name, Dictionary<string, object> payload, out ChildEntity entity) {
+ private IEntityCollection CreateChildCollection(string name, object obj, Type elementType) {
+ var collectionType = typeof(EntityCollection<>).MakeGenericType(elementType);
+ return (IEntityCollection)Activator.CreateInstance(collectionType, this, name, obj);
+ }
+
+ public virtual void Dispose() {
+ }
+
+
+ public abstract void ProcessIncomingUpdate(object payload, PayloadDelegate reply);
+ }
+
+ // Entity represents a single exposed object, and holds information about its exposed children.
+ public abstract class Entity : EntityOrCollectionBase, IEntity
+ {
+ private readonly List<IFacet> facets = new List<IFacet>();
+
+ public Entity() { }
+
+ public override void Dispose() {
+ base.Dispose();
+ foreach (var facet in facets) {
+ facet.Dispose();
+ }
+ }
+
+ public void AddFacet(IFacet facet) {
+ facets.Insert(0, facet);
+ facet.AddedTo(this);
+ }
+
+ public void AddNativeObject(object obj) {
+ AddFacet(new ReflectionFacet(this, obj));
+ }
+
+ public bool TryResolve(string name, object payload, out IChildEntityOrCollection entity) {
if (children.TryGetValue(name, out entity))
return true;
@@ -212,7 +299,14 @@ public abstract class Entity
return false;
}
- public void ProcessIncomingUpdate(IDictionary<string, object> payload, PayloadDelegate reply) {
+ public override void ProcessIncomingUpdate(object payload, PayloadDelegate reply) {
+ if (payload is D)
+ ProcessIncomingDictionary((D)payload, reply);
+ else
+ throw new ArgumentException("Unsupported payload");
+ }
+
+ private void ProcessIncomingDictionary(IDictionary<string, object> payload, PayloadDelegate reply) {
// properties
var properties = payload.Where(e => !e.Key.StartsWith("#") && !e.Key.StartsWith("!")).ToDictionary(e => e.Key, e => e.Value);
foreach (IFacet facet in facets)
@@ -227,9 +321,9 @@ public abstract class Entity
// children
foreach (var entry in payload.Where(e => e.Key.StartsWith("#"))) {
string name = entry.Key.Substring(1);
- var childPayload = (Dictionary<string, object>)entry.Value;
+ var childPayload = entry.Value;
- ChildEntity child;
+ IChildEntityOrCollection child;
// childPayload can be mutated by this call
if (!TryResolve(name, childPayload, out child))
throw new PayloadException("Cannot resolve child named: " + name);
@@ -248,19 +342,17 @@ public abstract class Entity
}
}
}
-
- public abstract void SendUpdate(Dictionary<string, object> payload);
}
// All entites created via Expose or Resolve calls are ChildEntities
// (that is, all entities except for the root one).
- public class ChildEntity : Entity
+ public class ChildEntity : Entity, IChildEntity
{
- public readonly Entity parent;
+ public readonly EntityOrCollectionBase parent;
public readonly string name;
public readonly object obj;
- public ChildEntity(Entity parent, string name, object obj) {
+ public ChildEntity(EntityOrCollectionBase parent, string name, object obj) {
this.parent = parent;
this.name = name;
this.obj = obj;
@@ -283,6 +375,10 @@ public class ChildEntity : Entity
public override void SendUpdate(Dictionary<string, object> payload) {
parent.SendUpdate(new Dictionary<string, object> { { "#" + name, payload } });
}
+
+ public object NativeObject {
+ get { return obj; }
+ }
}
// The root entity does not correspond to any native objects, and is used to gain access to the real exposed objects.
@@ -318,6 +414,73 @@ public class RootEntity : Entity
}
}
+ public class EntityCollection<T> : EntityOrCollectionBase, IEntityCollection where T : new()
+ {
+ public readonly Entity parent;
+ public readonly string name;
+ public readonly ObservableCollection<T> collection;
+
+ public EntityCollection(Entity parent, string name, ObservableCollection<T> obj) {
+ this.parent = parent;
+ this.name = name;
+ this.collection = obj;
+ }
+
+ public override RootEntity Root {
+ get { return parent.Root; }
+ }
+
+ public override string Path {
+ get { return parent.PathPrefix + "#" + name; }
+ }
+
+ public override string PathPrefix {
+ get { return parent.PathPrefix + "#" + name + " "; }
+ }
+
+ public override void SendUpdate(Dictionary<string, object> payload) {
+ parent.SendUpdate(new D { { "#" + name, payload } });
+ }
+
+ public override void ProcessIncomingUpdate(object payload, PayloadDelegate reply) {
+ if (payload is IList<object>) {
+ ProcessIncomingList((IList<object>)payload, reply);
+ } else if (payload is D) {
+ ProcessIncomingDictionary((D)payload, reply);
+ } else {
+ throw new ArgumentException("Unsupported collection payload");
+ }
+ }
+
+ private void ProcessIncomingList(IList<object> payload, PayloadDelegate reply) {
+ collection.Clear();
+
+ var itemIds = children.Keys.ToArray();
+ foreach (var itemId in itemIds)
+ Unexpose(itemId);
+
+ foreach (var itemRaw in payload) {
+ var itemPayload = (D)itemRaw;
+ var itemId = (string)itemPayload["id"];
+
+ var item = new T();
+ var child = Expose(itemId, item);
+ child.ProcessIncomingUpdate(itemPayload, reply);
+ collection.Add(item);
+ }
+ }
+
+ private void ProcessIncomingDictionary(D payload, PayloadDelegate reply) {
+ }
+
+ private void UpdateSubentities() {
+ }
+
+ public object NativeObject {
+ get { return collection; }
+ }
+ }
+
public class FacetRegistration
{
private readonly Type objType;
@@ -328,9 +491,9 @@ public class FacetRegistration
this.facetType = facetType;
}
- public bool TryCreate(ChildEntity obj, out IFacet facet) {
- if (objType.IsAssignableFrom(obj.obj.GetType())) {
- facet = (IFacet)Activator.CreateInstance(facetType, obj, obj.obj);
+ public bool TryCreate(IChildEntity entity, out IFacet facet) {
+ if (objType.IsAssignableFrom(entity.NativeObject.GetType())) {
+ facet = (IFacet)Activator.CreateInstance(facetType, entity, entity.NativeObject);
return true;
} else {
facet = null;
View
20 windows/Twins/WPF/Facets.cs
@@ -185,7 +185,7 @@ class TreeViewFacet : Facet<TreeView>
public TreeViewFacet(Entity entity, TreeView obj)
: base(entity, obj) {
- //obj.MouseDoubleClick += OnMouseDoubleClick;
+ //collection.MouseDoubleClick += OnMouseDoubleClick;
obj.SelectedItemChanged += OnSelectedItemChanged;
obj.ItemsSource = items;
}
@@ -246,21 +246,21 @@ public TreeViewFacet(Entity entity, TreeView obj)
}
}
- //private void SelectItemHelper(TreeViewItem item)
+ //private void SelectItemHelper(TreeViewItem itemPayload)
//{
- // if (item == null)
+ // if (itemPayload == null)
// return;
- // SelectItemHelper(item.Parent as TreeViewItem);
- // if (!item.IsExpanded)
+ // SelectItemHelper(itemPayload.Parent as TreeViewItem);
+ // if (!itemPayload.IsExpanded)
// {
- // item.IsExpanded = true;
- // item.UpdateLayout();
+ // itemPayload.IsExpanded = true;
+ // itemPayload.UpdateLayout();
// }
//}
- //private void SelectItem(TreeViewItem item) // QND solution
+ //private void SelectItem(TreeViewItem itemPayload) // QND solution
//{
- // SelectItemHelper(item.Parent as TreeViewItem);
- // item.IsSelected = true;
+ // SelectItemHelper(itemPayload.Parent as TreeViewItem);
+ // itemPayload.IsSelected = true;
//}
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
View
6 windows/src/MainWindowViewModel.cs
@@ -39,6 +39,12 @@ public class MainWindowViewModel : ModelBase
}
}
+ public ObservableCollection<ActionGroup> ActionGroups {
+ get {
+ return actionsFiles.Groups;
+ }
+ }
+
public string Dummy {
get {
return dummy;
Please sign in to comment.
Something went wrong with that request. Please try again.