diff --git a/Perspex.Application/Application.cs b/Perspex.Application/Application.cs index 65d94db76a..8247149225 100644 --- a/Perspex.Application/Application.cs +++ b/Perspex.Application/Application.cs @@ -7,6 +7,7 @@ namespace Perspex { using System; + using System.Threading; using Perspex.Controls; using Perspex.Input; using Perspex.Styling; @@ -86,9 +87,9 @@ namespace Perspex public void Run(ICloseable closable) { - DispatcherFrame frame = new DispatcherFrame(); - closable.Closed += (s, e) => frame.Continue = false; - Dispatcher.PushFrame(frame); + var source = new CancellationTokenSource(); + closable.Closed += (s, e) => source.Cancel(); + Dispatcher.UIThread.MainLoop(source.Token); } } } diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index fcdf7b7ee7..0b224ac578 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -49,9 +49,31 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/Perspex.Base/Platform/IPlatformThreadingInterface.cs index fa71e33956..1b752ef297 100644 --- a/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -7,14 +7,28 @@ namespace Perspex.Platform { using System; + using System.Threading; + using System.Threading.Tasks; using Perspex.Threading; public interface IPlatformThreadingInterface { - Dispatcher GetThreadDispatcher(); + /// + /// Process a single message from the windowing system, blocking until one is available. + /// + void ProcessMessage(); - void KillTimer(object timerHandle); + /// + /// Starts a timer. + /// + /// The interval. + /// The action to call on each tick. + /// An used to stop the timer. + IDisposable StartTimer(TimeSpan interval, Action internalTick); - object StartTimer(TimeSpan interval, Action internalTick); + /// + /// Sends a message that causes to exit. + /// + void Wake(); } } diff --git a/Perspex.Base/Threading/Dispatcher.cs b/Perspex.Base/Threading/Dispatcher.cs index 8a832c4b6a..b3a7c1d7ea 100644 --- a/Perspex.Base/Threading/Dispatcher.cs +++ b/Perspex.Base/Threading/Dispatcher.cs @@ -1,102 +1,39 @@ // ----------------------------------------------------------------------- // -// Copyright 2013 MIT Licence. See licence.md for more information. +// Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Threading { using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Security; using System.Threading; - using Perspex.Platform; - using Splat; + using System.Threading.Tasks; + using Perspex.Win32.Threading; - public enum DispatcherPriority + public class Dispatcher { - Invalid = -1, - Inactive = 0, - SystemIdle = 1, - ApplicationIdle = 2, - ContextIdle = 3, - Background = 4, - Input = 5, - Loaded = 6, - Render = 7, - DataBind = 8, - Normal = 9, - Send = 10, - } + private static Dispatcher instance = new Dispatcher(); - [Flags] - internal enum Flags - { - ShutdownStarted = 1, - Shutdown = 2, - Disabled = 4 - } - - public abstract class Dispatcher - { - private static DispatcherFrame mainExecutionFrame = new DispatcherFrame(); + private MainLoop mainLoop = new MainLoop(); - public static Dispatcher CurrentDispatcher + private Dispatcher() { - get { return Locator.Current.GetService().GetThreadDispatcher(); } } - public abstract bool HasShutdownFinished + public static Dispatcher UIThread { - get; + get { return instance; } } - public abstract DispatcherFrame CurrentFrame + public void MainLoop(CancellationToken cancellationToken) { - get; - set; + this.mainLoop.Run(cancellationToken); } - public static void PushFrame(DispatcherFrame frame) + public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - if (frame == null) - { - throw new ArgumentNullException("frame"); - } - - Dispatcher dis = CurrentDispatcher; - - if (dis.HasShutdownFinished) - { - throw new InvalidOperationException("The Dispatcher has shut down"); - } - - if (frame.Running != null) - { - throw new InvalidOperationException("Frame is already running on a different dispatcher"); - } - - frame.ParentFrame = dis.CurrentFrame; - dis.CurrentFrame = frame; - - frame.Running = dis; - - dis.RunFrame(frame); - } - - - public static void Run() - { - PushFrame(mainExecutionFrame); + return this.mainLoop.InvokeAsync(action, priority); } - - public abstract DispatcherOperation BeginInvoke(Action method); - - public abstract DispatcherOperation BeginInvoke(DispatcherPriority priority, Action method); - - protected internal abstract void Reprioritize(DispatcherOperation op, DispatcherPriority oldpriority); - - protected abstract void RunFrame(DispatcherFrame frame); } } \ No newline at end of file diff --git a/Perspex.Base/Threading/DispatcherFrame.cs b/Perspex.Base/Threading/DispatcherFrame.cs deleted file mode 100644 index 4cb1b3d97a..0000000000 --- a/Perspex.Base/Threading/DispatcherFrame.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright 2013 MIT Licence. See licence.md for more information. -// -// ----------------------------------------------------------------------- - -namespace Perspex.Threading -{ - public class DispatcherFrame - { - public DispatcherFrame() - { - this.Continue = true; - this.ExitOnRequest = true; - } - - public bool Continue { get; set; } - - public bool ExitOnRequest { get; set; } - - public Dispatcher Running { get; set; } - - public DispatcherFrame ParentFrame { get; set; } - } -} diff --git a/Perspex.Base/Threading/DispatcherOperation.cs b/Perspex.Base/Threading/DispatcherOperation.cs deleted file mode 100644 index e9c04c9dbe..0000000000 --- a/Perspex.Base/Threading/DispatcherOperation.cs +++ /dev/null @@ -1,159 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright 2013 MIT Licence. See licence.md for more information. -// -// ----------------------------------------------------------------------- - -namespace Perspex.Threading -{ - using System; - using System.Security; - - public enum DispatcherOperationStatus - { - Pending = 0, - Aborted = 1, - Completed = 2, - Executing = 3 - } - - public sealed class DispatcherOperation - { - private DispatcherOperationStatus status; - private DispatcherPriority priority; - private Dispatcher dispatcher; - private object result; - private Delegate delegateMethod; - private object[] delegateArgs; - - public DispatcherOperation(Dispatcher dis, DispatcherPriority prio) - { - this.dispatcher = dis; - this.priority = prio; - if (this.Dispatcher.HasShutdownFinished) - { - this.status = DispatcherOperationStatus.Aborted; - } - else - { - this.status = DispatcherOperationStatus.Pending; - } - } - - public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d) - : this(dis, prio) - { - this.delegateMethod = d; - } - - public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d, object arg) - : this(dis, prio) - { - this.delegateMethod = d; - this.delegateArgs = new object[1]; - this.delegateArgs[0] = arg; - } - - public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d, object arg, object[] args) - : this(dis, prio) - { - this.delegateMethod = d; - this.delegateArgs = new object[args.Length + 1]; - this.delegateArgs[0] = arg; - Array.Copy(args, 1, this.delegateArgs, 0, args.Length); - } - - public event EventHandler Completed; - - public DispatcherOperationStatus Status - { - get - { - return this.status; - } - - internal set - { - this.status = value; - } - } - - public Dispatcher Dispatcher - { - get - { - return this.dispatcher; - } - } - - public DispatcherPriority Priority - { - get - { - return this.priority; - } - - set - { - if (this.priority != value) - { - DispatcherPriority old = this.priority; - this.priority = value; - this.dispatcher.Reprioritize(this, old); - } - } - } - - public object Result - { - get - { - return this.result; - } - } - - public bool Abort() - { - this.status = DispatcherOperationStatus.Aborted; - throw new NotImplementedException(); - } - - public DispatcherOperationStatus Wait() - { - if (this.status == DispatcherOperationStatus.Executing) - { - throw new InvalidOperationException("Already executing"); - } - - throw new NotImplementedException(); - } - - [SecurityCritical] - public DispatcherOperationStatus Wait(TimeSpan timeout) - { - if (this.status == DispatcherOperationStatus.Executing) - { - throw new InvalidOperationException("Already executing"); - } - - throw new NotImplementedException(); - } - - public void Invoke() - { - this.status = DispatcherOperationStatus.Executing; - - if (this.delegateMethod != null) - { - this.result = this.delegateMethod.DynamicInvoke(this.delegateArgs); - } - - this.status = DispatcherOperationStatus.Completed; - - if (this.Completed != null) - { - this.Completed(this, EventArgs.Empty); - } - } - } -} \ No newline at end of file diff --git a/Perspex.Base/Threading/DispatcherPriority.cs b/Perspex.Base/Threading/DispatcherPriority.cs new file mode 100644 index 0000000000..00fb1df484 --- /dev/null +++ b/Perspex.Base/Threading/DispatcherPriority.cs @@ -0,0 +1,24 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Threading +{ + public enum DispatcherPriority + { + Invalid = -1, + Inactive = 0, + SystemIdle = 1, + ApplicationIdle = 2, + ContextIdle = 3, + Background = 4, + Input = 5, + Loaded = 6, + Render = 7, + DataBind = 8, + Normal = 9, + Send = 10, + } +} diff --git a/Perspex.Base/Threading/DispatcherTimer.cs b/Perspex.Base/Threading/DispatcherTimer.cs index c1289107bc..b7912fa983 100644 --- a/Perspex.Base/Threading/DispatcherTimer.cs +++ b/Perspex.Base/Threading/DispatcherTimer.cs @@ -12,7 +12,7 @@ namespace Perspex.Threading public class DispatcherTimer { - private object timerHandle; + private IDisposable timer; private DispatcherPriority priority; @@ -21,13 +21,13 @@ namespace Perspex.Threading public DispatcherTimer() { this.priority = DispatcherPriority.Normal; - this.Dispatcher = Dispatcher.CurrentDispatcher; + this.Dispatcher = Dispatcher.UIThread; } public DispatcherTimer(DispatcherPriority priority) { this.priority = priority; - this.Dispatcher = Dispatcher.CurrentDispatcher; + this.Dispatcher = Dispatcher.UIThread; } public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher) @@ -46,7 +46,7 @@ namespace Perspex.Threading ~DispatcherTimer() { - if (this.timerHandle != null) + if (this.timer != null) { this.Stop(); } @@ -80,7 +80,7 @@ namespace Perspex.Threading { get { - return this.timerHandle != null; + return this.timer != null; } set @@ -110,7 +110,7 @@ namespace Perspex.Threading if (!this.IsEnabled) { IPlatformThreadingInterface threading = Locator.Current.GetService(); - this.timerHandle = threading.StartTimer(this.Interval, this.InternalTick); + this.timer = threading.StartTimer(this.Interval, this.InternalTick); } } @@ -119,14 +119,14 @@ namespace Perspex.Threading if (this.IsEnabled) { IPlatformThreadingInterface threading = Locator.Current.GetService(); - threading.KillTimer(this.timerHandle); - this.timerHandle = null; + this.timer.Dispose(); + this.timer = null; } } private void InternalTick() { - this.Dispatcher.BeginInvoke(this.priority, (Action)this.RaiseTick); + this.Dispatcher.InvokeAsync(this.RaiseTick, this.priority); } private void RaiseTick() diff --git a/Perspex.Base/Threading/MainLoop.cs b/Perspex.Base/Threading/MainLoop.cs new file mode 100644 index 0000000000..0e1fe67ef2 --- /dev/null +++ b/Perspex.Base/Threading/MainLoop.cs @@ -0,0 +1,84 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Win32.Threading +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using NGenerics.DataStructures.Queues; + using Perspex.Platform; + using Perspex.Threading; + using Splat; + + internal class MainLoop + { + private static IPlatformThreadingInterface platform; + + private PriorityQueue queue = + new PriorityQueue(PriorityQueueType.Maximum); + + static MainLoop() + { + platform = Locator.Current.GetService(); + } + + public void Run(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + Job job; + + // TODO: Dispatch windows messages in preference to lower priority jobs. + while (queue.Count > 0) + { + lock (this.queue) + { + job = queue.Dequeue(); + } + + try + { + job.Action(); + job.TaskCompletionSource.SetResult(null); + } + catch (Exception e) + { + job.TaskCompletionSource.SetException(e); + } + } + + platform.ProcessMessage(); + } + } + + public Task InvokeAsync(Action action, DispatcherPriority priority) + { + var job = new Job(action); + + lock (queue) + { + this.queue.Add(job, priority); + } + + platform.Wake(); + return job.TaskCompletionSource.Task; + } + + private class Job + { + public Job(Action action) + { + this.Action = action; + this.TaskCompletionSource = new TaskCompletionSource(); + } + + public Action Action { get; private set; } + + public TaskCompletionSource TaskCompletionSource { get; set; } + } + } +} diff --git a/Perspex.Base/Threading/NGenerics/BinarySearchTreeBase.cs b/Perspex.Base/Threading/NGenerics/BinarySearchTreeBase.cs new file mode 100644 index 0000000000..c4d77aa9be --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/BinarySearchTreeBase.cs @@ -0,0 +1,551 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using NGenerics.Comparers; +using NGenerics.Patterns.Visitor; +using NGenerics.Util; + +namespace NGenerics.DataStructures.Trees +{ + + /// + /// A base class for Binary Search Trees that store a single value in each node. + /// + /// + //[Serializable] + internal abstract class BinarySearchTreeBase : ISearchTree + { + + #region Globals + + internal const string alreadyContainedInTheTree = "The item is already contained in the tree."; + private BinaryTree tree; + private readonly IComparer comparer; + + #endregion + + #region Delegates + + /// + /// A custom comparison between some search value and the type of item that is kept in the tree. + /// + /// The type of the search. + protected delegate int CustomComparison(TSearch value, T item); + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the class. + /// + protected BinarySearchTreeBase() + { + comparer = Comparer.Default; + } + + /// + /// Initializes a new instance of the class. + /// + /// The comparer to use when comparing items. + /// is a null reference (Nothing in Visual Basic). + protected BinarySearchTreeBase(IComparer comparer) + { + Guard.ArgumentNotNull(comparer, "comparer"); + this.comparer = comparer; + } + + /// + /// Initializes a new instance of the class. + /// + /// The comparison. + protected BinarySearchTreeBase(Comparison comparison) + { + Guard.ArgumentNotNull(comparison, "comparison"); + comparer = new ComparisonComparer(comparison); + } + + #endregion + + #region Public Members + + /// + /// Gets the comparer. + /// + /// The comparer. + public IComparer Comparer + { + get + { + return comparer; + } + } + + #endregion + + #region Protected Members + + /// + /// Finds the node containing the specified data key. + /// + /// The item. + /// + /// The node with the specified key if found. If the key is not in the tree, this method returns null. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + protected virtual BinaryTree FindNode(T item) + { + if (tree == null) + { + return null; + } + + var currentNode = tree; + + while (currentNode != null) + { + var nodeResult = comparer.Compare(item, currentNode.Data); + + if (nodeResult == 0) + { + return currentNode; + } + + currentNode = nodeResult < 0 ? currentNode.Left : currentNode.Right; + } + + return null; + } + + /// + /// Finds the node that matches the custom delegate. + /// + /// The type of the search. + /// The value. + /// The custom comparison. + /// The item if found, else null. + protected virtual BinaryTree FindNode(TSearch value, CustomComparison customComparison) + { + if (tree == null) + { + return null; + } + + var currentNode = tree; + + while (currentNode != null) + { + var nodeResult = customComparison(value, currentNode.Data); + + if (nodeResult == 0) + { + return currentNode; + } + + currentNode = nodeResult < 0 ? currentNode.Left : currentNode.Right; + } + + return null; + } + + /// + /// Removes the item from the tree. + /// + /// The item to remove. + /// An indication of whether the item has been removed from the tree. + protected abstract bool RemoveItem(T item); + + /// + /// Adds the item. + /// + /// The item. + protected abstract void AddItem(T item); + + + /// + /// Find the maximum node. + /// + /// The maximum node. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + protected BinaryTree FindMaximumNode() + { + #region Debug + + Debug.Assert(tree != null); + + #endregion + + return FindMaximumNode(tree); + } + + + /// + /// Find the minimum node. + /// + /// The minimum node. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + protected BinaryTree FindMinimumNode() + { + #region Debug + + Debug.Assert(tree != null); + + #endregion + + return FindMinimumNode(tree); + } + + /// + /// Finds the maximum node. + /// + /// The start node. + /// The maximum node below this node. + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + protected static BinaryTree FindMaximumNode(BinaryTree startNode) + { + #region Asserts + + Debug.Assert(startNode != null); + + #endregion + + var searchNode = startNode; + + while (searchNode.Right != null) + { + searchNode = searchNode.Right; + } + + return searchNode; + } + + + /// + /// Finds the minimum node. + /// + /// The start node. + /// The minimum node below this node. + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + protected static BinaryTree FindMinimumNode(BinaryTree startNode) + { + #region Asserts + + Debug.Assert(startNode != null); + + #endregion + + var searchNode = startNode; + + while (searchNode.Left != null) + { + searchNode = searchNode.Left; + } + + return searchNode; + } + + /// + /// Gets or sets the for this . + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + protected BinaryTree Tree + { + get + { + return tree; + } + set + { + tree = value; + } + } + + #endregion + + #region Private Members + + /// + /// Visits the node in an in-order fashion. + /// + /// The node. + /// The visitor. + private static void VisitNode(BinaryTree node, OrderedVisitor visitor) + { + if (node != null) + { + var pair = node.Data; + + visitor.VisitPreOrder(pair); + + VisitNode(node.Left, visitor); + + visitor.VisitInOrder(pair); + + VisitNode(node.Right, visitor); + + visitor.VisitPostOrder(pair); + } + } + + #endregion + + #region ISearchTree Members + + /// + /// + /// + /// + /// + public virtual T Minimum + { + get + { + #region Validation + + ValidateEmpty(); + + #endregion + + return FindMinimumNode().Data; + } + } + + private void ValidateEmpty() + { + if (Count == 0) + { + throw new InvalidOperationException("The search tree is empty."); + } + } + + /// + /// + /// + /// + /// + public virtual T Maximum + { + get + { + ValidateEmpty(); + + return FindMaximumNode().Data; + } + } + + + + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public void DepthFirstTraversal(OrderedVisitor visitor) + { + Guard.ArgumentNotNull(visitor, "visitor"); + + VisitNode(tree, visitor); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerator GetOrderedEnumerator() + { + if (tree != null) + { + var trackingVisitor = new TrackingVisitor(); + var inOrderVisitor = new InOrderVisitor(trackingVisitor); + + tree.DepthFirstTraversal(inOrderVisitor); + + var trackingList = trackingVisitor.TrackingList; + + for (var i = 0; i < trackingList.Count; i++) + { + yield return trackingList[i]; + } + } + } + + /// + /// + /// + /// + /// + public bool IsEmpty + { + get + { + return Count == 0; + } + } + + /// + public bool Remove(T item) + { + var itemRemoved = RemoveItem(item); + + if (itemRemoved) + { + Count--; + } + + return itemRemoved; + } + + /// + /// + /// + /// + /// + public void Clear() + { + ClearItems(); + } + + /// + /// Clears all the objects in this instance. + /// + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void ClearItems() + { + tree = null; + Count = 0; + } + + /// + /// + /// + /// + /// + public int Count { get; private set; } + + #endregion + + #region IEnumerable Members + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IEnumerator GetEnumerator() + { + if (tree != null) + { + var stack = new Stack>(); + + stack.Push(tree); + + while (stack.Count > 0) + { + var binaryTree = stack.Pop(); + + yield return binaryTree.Data; + + if (binaryTree.Left != null) + { + stack.Push(binaryTree.Left); + } + + if (binaryTree.Right != null) + { + stack.Push(binaryTree.Right); + } + } + } + } + + #endregion + + + #region ICollection Members + + /// + /// + /// + /// + /// + public void Add(T item) + { + AddItem(item); + Count++; + } + + /// + /// + /// + /// + /// + public virtual bool Contains(T item) + { + var node = FindNode(item); + return node != null; + } + + + /// + /// + /// + /// + /// + public void CopyTo(T[] array, int arrayIndex) + { + #region Validation + + Guard.ArgumentNotNull(array, "array"); + + if ((array.Length - arrayIndex) < Count) + { + throw new ArgumentException(Constants.NotEnoughSpaceInTheTargetArray, "array"); + } + + #endregion + + foreach (var association in tree) + { + array[arrayIndex++] = association; + } + } + /// + /// + /// + /// + /// + public bool IsReadOnly + { + get + { + return false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/BinaryTree.cs b/Perspex.Base/Threading/NGenerics/BinaryTree.cs new file mode 100644 index 0000000000..02f1b85096 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/BinaryTree.cs @@ -0,0 +1,714 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Collections; +using System.Collections.Generic; +using NGenerics.Patterns.Visitor; +using NGenerics.Util; +using System.Diagnostics.CodeAnalysis; + +namespace NGenerics.DataStructures.Trees +{ + /// + /// An implementation of a Binary Tree data structure. + /// + /// The type of elements in the . + //[Serializable] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + internal class BinaryTree : ICollection, ITree + { + #region Globals + + private BinaryTree leftSubtree; + private BinaryTree rightSubtree; + private T data; + + #endregion + + #region Construction + + /// The data contained in this node. + public BinaryTree(T data) : this(data, null, null) { } + + /// The data. + /// The data of the left subtree. + /// The data of the right subtree. + public BinaryTree(T data, T left, T right) : this(data, new BinaryTree(left), new BinaryTree(right)) { } + + /// The data contained in this node. + /// The left subtree. + /// The right subtree. + public BinaryTree(T data, BinaryTree left, BinaryTree right) + : this(data, left, right, true) + { + } + + + + /// The data contained in this node. + /// The left subtree. + /// The right subtree. + /// to validate ; otherwise . + internal BinaryTree(T data, BinaryTree left, BinaryTree right, bool validateData) + { + #region Validation + + //TODO: probably not the most efficient way of doing this but SplayTree needs to use a BinaryTree with null data. + if (validateData) + { + Guard.ArgumentNotNull(data, "data"); + } + + #endregion + + leftSubtree = left; + + if (left != null) + { + left.Parent = this; + } + + rightSubtree = right; + + if (right != null) + { + right.Parent = this; + } + + this.data = data; + } + + + + #endregion + + #region ICollection Members + + /// + public bool IsEmpty + { + get + { + return Count == 0; + } + } + + /// + public bool IsFull + { + get + { + return (leftSubtree != null) && (rightSubtree != null); + } + } + + /// + public bool Contains(T item) + { + foreach (var thisItem in this) + { + if (item.Equals(thisItem)) + { + return true; + } + } + + return false; + } + + /// + public void CopyTo(T[] array, int arrayIndex) + { + Guard.ArgumentNotNull(array, "array"); + + foreach (var item in this) + { + #region Validation + + if (arrayIndex >= array.Length) + { + throw new ArgumentException(Constants.NotEnoughSpaceInTheTargetArray, "array"); + } + + #endregion + + array[arrayIndex++] = item; + } + } + + /// + public int Count + { + get + { + var count = 0; + + if (leftSubtree != null) + { + count++; + } + + if (rightSubtree != null) + { + count++; + } + + return count; + } + } + + /// + public void Add(T item) + { + AddItem(new BinaryTree(item)); + } + + /// + public bool Remove(T item) + { + if (leftSubtree != null) + { + if (leftSubtree.data.Equals(item)) + { + RemoveLeft(); + return true; + } + } + + if (rightSubtree != null) + { + if (rightSubtree.data.Equals(item)) + { + RemoveRight(); + return true; + } + } + + return false; + } + + /// + /// Removes the specified child. + /// + /// The child. + /// A value indicating whether the child was found (and removed) from this tree. + public bool Remove(BinaryTree child) + { + if (leftSubtree != null) + { + if (leftSubtree == child) + { + RemoveLeft(); + return true; + } + } + + if (rightSubtree != null) + { + if (rightSubtree == child) + { + RemoveRight(); + return true; + } + } + + return false; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() + { + var stack = new Stack>(); + + stack.Push(this); + + while (stack.Count > 0) + { + var tree = stack.Pop(); + + yield return tree.Data; + + if (tree.leftSubtree != null) + { + stack.Push(tree.leftSubtree); + } + + if (tree.rightSubtree != null) + { + stack.Push(tree.rightSubtree); + } + } + } + + /// + public virtual void Clear() + { + if (leftSubtree != null) + { + leftSubtree.Parent = null; + leftSubtree = null; + } + + if (rightSubtree != null) + { + rightSubtree.Parent = null; + rightSubtree = null; + } + } + + #endregion + + #region ITree Members + + /// + void ITree.Add(ITree child) + { + AddItem((BinaryTree)child); + } + + /// + ITree ITree.GetChild(int index) + { + return GetChild(index); + } + + /// + bool ITree.Remove(ITree child) + { + return Remove((BinaryTree)child); + } + + /// + ITree ITree.FindNode(Predicate condition) + { + return FindNode(condition); + } + + /// + ITree ITree.Parent + { + get + { + return Parent; + } + } + + #endregion + + #region Public Members + + /// + /// Gets the parent of the current node.. + /// + /// The parent of the current node. + public BinaryTree Parent { get; private set; } + + /// + /// Finds the node with the specified condition. If a node is not found matching + /// the specified condition, null is returned. + /// + /// The condition to test. + /// The first node that matches the condition supplied. If a node is not found, null is returned. + /// is a null reference (Nothing in Visual Basic). + public BinaryTree FindNode(Predicate condition) + { + Guard.ArgumentNotNull(condition, "condition"); + + if (condition(Data)) + { + return this; + } + + if (leftSubtree != null) + { + var ret = leftSubtree.FindNode(condition); + + if (ret != null) + { + return ret; + } + } + + if (rightSubtree != null) + { + var ret = rightSubtree.FindNode(condition); + + if (ret != null) + { + return ret; + } + } + + return null; + } + + /// + /// Gets or sets the left subtree. + /// + /// The left subtree. + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual BinaryTree Left + { + get + { + return leftSubtree; + } + set + { + if (leftSubtree != null) + { + RemoveLeft(); + } + + if (value != null) + { + if (value.Parent != null) + { + value.Parent.Remove(value); + } + + value.Parent = this; + } + + leftSubtree = value; + } + } + + /// + /// Gets or sets the right subtree. + /// + /// The right subtree. + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual BinaryTree Right + { + get + { + return rightSubtree; + } + set + { + + if (rightSubtree != null) + { + RemoveRight(); + } + + if (value != null) + { + if (value.Parent != null) + { + value.Parent.Remove(value); + } + + value.Parent = this; + } + + rightSubtree = value; + } + } + + /// + public virtual T Data + { + get + { + return data; + } + set + { + #region Validation + + Guard.ArgumentNotNull(value, "data"); + + #endregion + + data = value; + } + } + + /// + public int Degree + { + get + { + return Count; + } + } + + /// + /// Gets the child at the specified index. + /// + /// The index of the child in question. + /// The child at the specified index. + /// does not equal 0 or 1. + public BinaryTree GetChild(int index) + { + switch (index) + { + case 0: + return leftSubtree; + case 1: + return rightSubtree; + default: + throw new ArgumentOutOfRangeException("index"); + } + } + /// + public virtual int Height + { + get + { + if (Degree == 0) + { + return 0; + } + + return 1 + FindMaximumChildHeight(); + } + } + + + /// + /// Performs a depth first traversal on this tree with the specified visitor. + /// + /// The ordered visitor. + /// is a null reference (Nothing in Visual Basic). + public virtual void DepthFirstTraversal(OrderedVisitor orderedVisitor) + { + Guard.ArgumentNotNull(orderedVisitor, "orderedVisitor"); + + if (orderedVisitor.HasCompleted) + { + return; + } + + // Preorder visit + orderedVisitor.VisitPreOrder(Data); + + if (leftSubtree != null) + { + leftSubtree.DepthFirstTraversal(orderedVisitor); + } + + // In-order visit + orderedVisitor.VisitInOrder(data); + + if (rightSubtree != null) + { + rightSubtree.DepthFirstTraversal(orderedVisitor); + } + + // PostOrder visit + orderedVisitor.VisitPostOrder(Data); + } + + /// + /// Performs a breadth first traversal on this tree with the specified visitor. + /// + /// The visitor. + /// is a null reference (Nothing in Visual Basic). + public virtual void BreadthFirstTraversal(IVisitor visitor) + { + Guard.ArgumentNotNull(visitor, "visitor"); + + var queue = new Queue>(); + + queue.Enqueue(this); + + while (queue.Count > 0) + { + if (visitor.HasCompleted) + { + break; + } + + var binaryTree = queue.Dequeue(); + visitor.Visit(binaryTree.Data); + + for (var i = 0; i < binaryTree.Degree; i++) + { + var child = binaryTree.GetChild(i); + + if (child != null) + { + queue.Enqueue(child); + } + } + } + } + + + /// + public virtual bool IsLeafNode + { + get + { + return Degree == 0; + } + } + + /// + /// Removes the left child. + /// + public virtual void RemoveLeft() + { + if (leftSubtree != null) + { + leftSubtree.Parent = null; + leftSubtree = null; + } + } + + /// + /// Removes the left child. + /// + public virtual void RemoveRight() + { + if (rightSubtree != null) + { + rightSubtree.Parent = null; + rightSubtree = null; + } + } + + /// + /// Adds an item to the . + /// + /// The subtree. + /// The is read-only. + /// The is full. + /// is null (Nothing in Visual Basic). + public void Add(BinaryTree subtree) + { + Guard.ArgumentNotNull(subtree, "subtree"); + + AddItem(subtree); + } + + /// + /// Adds an item to the . + /// + /// The subtree. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void AddItem(BinaryTree subtree) + { + if (leftSubtree == null) + { + if (subtree.Parent != null) + { + subtree.Parent.Remove(subtree); + } + + leftSubtree = subtree; + subtree.Parent = this; + } + else if (rightSubtree == null) + { + if (subtree.Parent != null) + { + subtree.Parent.Remove(subtree); + } + + rightSubtree = subtree; + subtree.Parent = this; + } + else + { + throw new InvalidOperationException("This binary tree is full."); + } + } + + + #endregion + + #region Private Members + + /// + /// Finds the maximum height between the child nodes. + /// + /// The maximum height of the tree between all paths from this node and all leaf nodes. + protected virtual int FindMaximumChildHeight() + { + var leftHeight = 0; + var rightHeight = 0; + + if (leftSubtree != null) + { + leftHeight = leftSubtree.Height; + } + + if (rightSubtree != null) + { + rightHeight = rightSubtree.Height; + } + + return leftHeight > rightHeight ? leftHeight : rightHeight; + } + + #endregion + + #region Operator Overloads + + /// + /// Gets the at the specified index. + /// + public BinaryTree this[int index] + { + get + { + return GetChild(index); + } + } + + #endregion + + #region ICollection Members + + /// + public bool IsReadOnly + { + get + { + return false; + } + } + + #endregion + + #region IEnumerable Members + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Object Members + + /// + public override string ToString() + { + return data.ToString(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/ComparisonComparer.cs b/Perspex.Base/Threading/NGenerics/ComparisonComparer.cs new file mode 100644 index 0000000000..b1c5a2a860 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/ComparisonComparer.cs @@ -0,0 +1,78 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Collections.Generic; +using NGenerics.Util; + +namespace NGenerics.Comparers +{ + /// + /// A Comparer using a Comparison delegate for comparisons between items. + /// + /// The type of the objects to compare. + //[Serializable] + internal sealed class ComparisonComparer : IComparer + { + #region Globals + + private Comparison comparison; + + #endregion + + #region Construction + + + /// The comparison. + /// is a null reference (Nothing in Visual Basic). + public ComparisonComparer(Comparison comparison) + { + Guard.ArgumentNotNull(comparison, "comparison"); + + this.comparison = comparison; + } + + #endregion + + #region Public Members + + /// + /// Gets or sets the comparison used in this comparer. + /// + /// The comparison used in this comparer. + /// is a null reference (Nothing in Visual Basic). + public Comparison Comparison + { + get + { + return comparison; + } + set + { + + Guard.ArgumentNotNull(value, "value"); + + comparison = value; + } + } + + #endregion + + #region IComparer Members + + /// + public int Compare(T x, T y) + { + return comparison(x, y); + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/Constants.cs b/Perspex.Base/Threading/NGenerics/Constants.cs new file mode 100644 index 0000000000..929c879ecd --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/Constants.cs @@ -0,0 +1,16 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + +namespace NGenerics +{ + static class Constants + { + public const string NotEnoughSpaceInTheTargetArray = "Not enough space in the target array."; + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/Guard.cs b/Perspex.Base/Threading/NGenerics/Guard.cs new file mode 100644 index 0000000000..bd3de2a291 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/Guard.cs @@ -0,0 +1,56 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; + +namespace NGenerics.Util +{ + /// + /// Performs common argument validation. + /// + internal static class Guard + { + #region Methods + + /// + /// Checks a string argument to ensure it isn't null or empty. + /// + /// The argument value to check. + /// The name of the argument. + /// is a null reference. + /// is . + public static void ArgumentNotNullOrEmptyString(string argumentValue, string argumentName) + { + ArgumentNotNull(argumentValue, argumentName); + + if (argumentValue.Length == 0) + { + throw new ArgumentException("String cannot be empty.", argumentName); + } + } + + + /// + /// Checks an argument to ensure it isn't null. + /// + /// The argument value to check. + /// The name of the argument. + /// is a null reference. + public static void ArgumentNotNull(object argumentValue, string argumentName) + { + if (argumentValue == null) + { + throw new ArgumentNullException(argumentName); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/IQueue.cs b/Perspex.Base/Threading/NGenerics/IQueue.cs new file mode 100644 index 0000000000..67490b4892 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/IQueue.cs @@ -0,0 +1,43 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace NGenerics.DataStructures.Queues +{ + /// + /// A queue interface. + /// + /// The type of the elements in the queue. + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + internal interface IQueue + { + /// + /// Enqueues the item at the back of the queue. + /// + /// The item. + void Enqueue(T item); + + /// + /// Dequeues the item at the front of the queue. + /// + /// The item at the front of the queue. + /// The is empty. + T Dequeue(); + + /// + /// Peeks at the item in the front of the queue, without removing it. + /// + /// The item at the front of the queue. + /// The is empty. + T Peek(); + } +} diff --git a/Perspex.Base/Threading/NGenerics/ISearchTree.cs b/Perspex.Base/Threading/NGenerics/ISearchTree.cs new file mode 100644 index 0000000000..6547a80faa --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/ISearchTree.cs @@ -0,0 +1,65 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using NGenerics.Patterns.Visitor; + +namespace NGenerics.DataStructures.Trees +{ + + /// + /// An interface for Search Trees that mimic a dictionary. + /// + /// The type of element to hold in the tree. + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + internal interface ISearchTree : ICollection { + /// + /// Gets the largest item in the tree. + /// + /// The largest item in the tree. + /// The is empty. + T Maximum { get; } + + /// + /// Gets the smallest item in the tree. + /// + /// The smallest item in the tree. + /// The is empty. + T Minimum { get; } + + /// + /// Performs a depth first traversal on the search tree. + /// + /// The visitor to use. + /// is a null reference (Nothing in Visual Basic). + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + void DepthFirstTraversal(OrderedVisitor visitor); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IEnumerator GetOrderedEnumerator(); + } +} diff --git a/Perspex.Base/Threading/NGenerics/ITree.cs b/Perspex.Base/Threading/NGenerics/ITree.cs new file mode 100644 index 0000000000..b7f49400d5 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/ITree.cs @@ -0,0 +1,77 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + +using System; + +namespace NGenerics.DataStructures.Trees { + /// + /// An interface for the tree data structure + /// + /// The type of elements in the tree. + internal interface ITree { + /// + /// Adds the specified child to the tree. + /// + /// The child to add.. + void Add(ITree child); + + /// + /// Gets the data held in this node. + /// + /// The data. + T Data { get; } + + /// + /// Gets the degree of this node. + /// + /// The degree of this node. + int Degree { get; } + + /// + /// Gets the child at the specified index. + /// + /// The index. + /// The child at the specified index. + ITree GetChild(int index); + + /// + /// Gets the height of this tree. + /// + /// The height of this tree. + int Height { get; } + + /// + /// Gets a value indicating whether this instance is leaf node. + /// + /// + /// true if this instance is leaf node; otherwise, false. + /// + bool IsLeafNode { get; } + + /// + /// Removes the specified child. + /// + /// The child. + /// An indication of whether the child was found (and removed) from this tree. + bool Remove(ITree child); + + /// + /// Finds the node for which the given predicate holds true. + /// + /// The condition to test on the data item. + /// The fist node that matches the condition if found, otherwise null. + ITree FindNode(Predicate condition); + + /// + /// Gets the parent of the current node. + /// + /// The parent of the current node. + ITree Parent { get;} + } +} diff --git a/Perspex.Base/Threading/NGenerics/IVisitor.cs b/Perspex.Base/Threading/NGenerics/IVisitor.cs new file mode 100644 index 0000000000..6ec7cb8852 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/IVisitor.cs @@ -0,0 +1,31 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +namespace NGenerics.Patterns.Visitor +{ + /// + /// Provides an interface for visitors. + /// + /// The type of objects to be visited. + internal interface IVisitor + { + /// + /// Gets a value indicating whether this instance is done performing it's work.. + /// + /// true if this instance is done; otherwise, false. + bool HasCompleted { get; } + + /// + /// Visits the specified object. + /// + /// The object to visit. + void Visit(T obj); + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/InOrderVisitor.cs b/Perspex.Base/Threading/NGenerics/InOrderVisitor.cs new file mode 100644 index 0000000000..dbe64f07dc --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/InOrderVisitor.cs @@ -0,0 +1,48 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +namespace NGenerics.Patterns.Visitor +{ + /// + /// An in order implementation of the class. + /// + /// The type of objects to be visited. + internal sealed class InOrderVisitor : OrderedVisitor + { + #region Construction + + /// The visitor. + public InOrderVisitor(IVisitor visitor) : base(visitor) { } + + #endregion + + #region OrderedVisitor Members + + /// + /// Visits the object in post order. + /// + /// The obj. + public override void VisitPostOrder(T obj) + { + // Do nothing. + } + + /// + /// Visits the object in pre order. + /// + /// The obj. + public override void VisitPreOrder(T obj) + { + // Do nothing. + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/KeyTrackingVisitor.cs b/Perspex.Base/Threading/NGenerics/KeyTrackingVisitor.cs new file mode 100644 index 0000000000..4265115586 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/KeyTrackingVisitor.cs @@ -0,0 +1,74 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System.Collections.Generic; + +namespace NGenerics.Patterns.Visitor +{ + /// + /// A visitor that tracks (stores) keys from KeyValuePAirs in the order they were visited. + /// + /// The type of the keys for the items to be visited. + /// The type of the values for the items to be visited. + internal sealed class KeyTrackingVisitor : IVisitor> + { + #region Globals + + private readonly List tracks; + + #endregion + + #region Construction + + /// + public KeyTrackingVisitor() + { + tracks = new List(); + } + + #endregion + + #region Public Members + + /// + /// Gets the tracking list. + /// + /// The tracking list. + public IList TrackingList + { + get + { + return tracks; + } + } + + #endregion + + #region IVisitor> Members + + + /// + public void Visit(KeyValuePair obj) + { + tracks.Add(obj.Key); + } + + /// + public bool HasCompleted + { + get + { + return false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/KeyValuePairComparer.cs b/Perspex.Base/Threading/NGenerics/KeyValuePairComparer.cs new file mode 100644 index 0000000000..05f14d1342 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/KeyValuePairComparer.cs @@ -0,0 +1,86 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Collections.Generic; + +namespace NGenerics.Comparers { + /// + /// A comparer for comparing keys for the KeyValuePair class. + /// + /// The key type. + /// The value type. + //[Serializable] + internal class KeyValuePairComparer : IComparer> { + + #region Globals + + private readonly IComparer comparer; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the class. + /// + public KeyValuePairComparer() { + comparer = Comparer.Default; + } + + /// + /// Initializes a new instance of the class. + /// + /// The comparer. + public KeyValuePairComparer(IComparer comparer) { + + if (comparer == null) + { + throw new ArgumentNullException("comparer"); + } + + this.comparer = comparer; + } + + /// + /// Initializes a new instance of the class. + /// + /// The comparison. + public KeyValuePairComparer(Comparison comparison) + { + if (comparison == null) { + throw new ArgumentNullException("comparison"); + } + + comparer = new ComparisonComparer(comparison); + } + + #endregion + + #region IComparer> Members + + /// + public int Compare(KeyValuePair x, KeyValuePair y) { + return comparer.Compare(x.Key, y.Key); + } + + #endregion + + #region IComparer Members + + /// + public int Compare(TKey x, TKey y) { + return comparer.Compare(x, y); + } + + #endregion + + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/NodeColor.cs b/Perspex.Base/Threading/NGenerics/NodeColor.cs new file mode 100644 index 0000000000..1164b1b509 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/NodeColor.cs @@ -0,0 +1,18 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +namespace NGenerics.DataStructures.Trees +{ + internal enum NodeColor + { + Red = 0, + Black = 1 + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/OrderedVisitor.cs b/Perspex.Base/Threading/NGenerics/OrderedVisitor.cs new file mode 100644 index 0000000000..c639825d09 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/OrderedVisitor.cs @@ -0,0 +1,111 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Diagnostics.CodeAnalysis; +using NGenerics.Util; + +namespace NGenerics.Patterns.Visitor +{ + /// + /// A visitor that visits objects in order (PreOrder, PostOrder, or InOrder). + /// Used primarily as a base class for Visitors specializing in a specific order type. + /// + /// The type of objects to be visited. + internal class OrderedVisitor : IVisitor + { + #region Globals + + private readonly IVisitor visitorToUse; + + #endregion + + #region Construction + + /// The visitor to use when visiting the object. + /// is a null reference (Nothing in Visual Basic). + public OrderedVisitor(IVisitor visitorToUse) + { + Guard.ArgumentNotNull(visitorToUse, "visitorToUse"); + + this.visitorToUse = visitorToUse; + } + + #endregion + + #region IOrderedVisitor Members + + /// + /// Determines whether this visitor is done. + /// + /// + /// + /// true if this visitor is done; otherwise, false. + /// + public bool HasCompleted + { + get + { + return visitorToUse.HasCompleted; + } + } + + /// + /// Visits the object in pre order. + /// + /// The obj. + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PreOrder")] + public virtual void VisitPreOrder(T obj) + { + visitorToUse.Visit(obj); + } + + /// + /// Visits the object in post order. + /// + /// The obj. + public virtual void VisitPostOrder(T obj) + { + visitorToUse.Visit(obj); + } + + /// + /// Visits the object in order. + /// + /// The obj. + public virtual void VisitInOrder(T obj) + { + visitorToUse.Visit(obj); + } + /// + public void Visit(T obj) + { + visitorToUse.Visit(obj); + } + + #endregion + + #region Public Members + + /// + /// Gets the visitor to use. + /// + /// The visitor to use. + public IVisitor VisitorToUse + { + get + { + return visitorToUse; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/PriorityQueue.cs b/Perspex.Base/Threading/NGenerics/PriorityQueue.cs new file mode 100644 index 0000000000..f24cc0c41c --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/PriorityQueue.cs @@ -0,0 +1,557 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. + + Community contributions : + - TKey Peek(out TPriority) contributed by Karl Shulze (http://www.karlschulze.com/). +*/ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using NGenerics.Comparers; +using NGenerics.DataStructures.Trees; +using NGenerics.Util; + +namespace NGenerics.DataStructures.Queues +{ + /// + /// An implementation of a Priority Queue (can be or ). + /// + /// The type of the priority in the . + /// The type of the elements in the . + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + //[Serializable] + internal class PriorityQueue : ICollection, IQueue + { + #region Globals + + private readonly RedBlackTreeList tree; + private TPriority defaultPriority; + private readonly PriorityQueueType queueType; + + #endregion + + #region Construction + + /// Type of the queue. + /// + /// + /// + /// + public PriorityQueue(PriorityQueueType queueType) : + this(queueType, Comparer.Default) { + + } + + /// + public PriorityQueue(PriorityQueueType queueType, IComparer comparer) { + if ((queueType != PriorityQueueType.Minimum) && (queueType != PriorityQueueType.Maximum)) { + throw new ArgumentOutOfRangeException("queueType"); + } + this.queueType = queueType; + tree = new RedBlackTreeList(comparer); + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the queue. + /// The comparison. + /// + public PriorityQueue(PriorityQueueType queueType, Comparison comparison) : + this(queueType, new ComparisonComparer(comparison)) { + + } + + #endregion + + #region IQueue Members + + /// + /// + /// + /// + /// + public void Enqueue(TValue item) + { + Add(item); + } + + /// + /// Enqueues the specified item. + /// + /// The item. + /// The priority. + /// + /// + /// + /// + public void Enqueue(TValue item, TPriority priority) + { + Add(item, priority); + } + + /// + /// Dequeues the item at the front of the queue. + /// + /// The item at the front of the queue. + /// + /// + /// + /// + public TValue Dequeue() + { + TPriority priority; + return Dequeue(out priority); + } + + + /// + /// Peeks at the item in the front of the queue, without removing it. + /// + /// The item at the front of the queue. + /// + /// + /// + /// + public TValue Peek() + { + var association = GetNextItem(); + + // Always dequeue in FIFO manner + return association.Value.First.Value; + } + + + /// + /// Peeks at the item in the front of the queue, without removing it. + /// + /// The priority of the item. + /// The item at the front of the queue. + /// + /// + /// + /// + public TValue Peek(out TPriority priority) + { + var association = GetNextItem(); + var item = association.Value.First.Value; + priority = association.Key; + return item; + } + + #endregion + + #region ICollection Members + + /// + /// + /// + /// + /// + public bool IsReadOnly + { + get + { + return false; + } + } + + #endregion + + #region IEnumerable Members + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// + /// + /// + /// + public IEnumerator GetEnumerator() + { + return tree.GetValueEnumerator(); + } + + #endregion + + #region Public Members + + /// + /// + /// + /// + /// + public int Count { get; private set; } + + /// + /// + /// + /// + /// + public bool Contains(TValue item) + { + return tree.ContainsValue(item); + } + + /// + /// + /// + /// + /// + public void CopyTo(TValue[] array, int arrayIndex) + { + #region Validation + + Guard.ArgumentNotNull(array, "array"); + + if ((array.Length - arrayIndex) < Count) + { + throw new ArgumentException(Constants.NotEnoughSpaceInTheTargetArray, "array"); + } + + #endregion + + + foreach (var association in tree) + { + var items = association.Value; + + foreach (var item in items) + { + array.SetValue(item, arrayIndex++); + } + } + } + + /// + /// + /// + /// + /// + public void Add(TValue item) + { + Add(item, defaultPriority); + } + + /// + /// Adds an item to the . + /// + /// The object to add to the . + /// The priority of the item. + /// The is read-only. + /// + /// + /// + /// + public void Add(TValue item, TPriority priority) + { + AddItem(item, priority); + } + + /// + public bool Remove(TValue item) + { + TPriority priority; + return Remove(item, out priority); + } + + /// + /// Returns an enumerator that iterates through the keys in the collection. + /// + /// A that can be used to iterate through the keys in the collection. + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerator> GetKeyEnumerator() + { + return tree.GetKeyEnumerator(); + } + + /// + /// + /// + /// + /// + public void Clear() + { + ClearItems(); + } + + /// + /// Dequeues the item from the head of the queue. + /// + /// The priority of the item to dequeue. + /// The item at the head of the queue. + /// The is empty. + /// + /// + /// + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#")] + public TValue Dequeue(out TPriority priority) + { + return DequeueItem(out priority); + } + + /// + /// Dequeues the item at the front of the queue. + /// + /// The item at the front of the queue. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the or methods. + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#")] + protected virtual TValue DequeueItem(out TPriority priority) + { + var association = GetNextItem(); + + var item = association.Value.First.Value; + association.Value.RemoveFirst(); + + var key = association.Key; + + if (association.Value.Count == 0) + { + tree.Remove(association.Key); + } + + Count--; + + priority = key; + return item; + } + + /// + /// Gets or sets the default priority. + /// + /// The default priority. + public TPriority DefaultPriority + { + get + { + return defaultPriority; + } + set + { + defaultPriority = value; + } + } + + /// + /// Removes the first occurrence of the specified item from the property queue. + /// + /// The item to remove. + /// The priority associated with the item. + /// true if the item exists in the and has been removed; otherwise false. + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + public bool Remove(TValue item, out TPriority priority) + { + return RemoveItem(item, out priority); + } + /// + /// Removes the item. + /// + /// The item to remove + /// The priority of the item that was removed. + /// An indication of whether the item was found, and removed. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] + protected virtual bool RemoveItem(TValue item, out TPriority priority) + { + var removed = tree.Remove(item, out priority); + + if (removed) + { + Count--; + } + + return removed; + } + + /// + /// Removes the items with the specified priority. + /// + /// The priority. + /// true if the priority exists in the and has been removed; otherwise false. + public bool RemovePriorityGroup(TPriority priority) + { + return RemoveItems(priority); + } + + /// + /// Removes the items from the collection with the specified priority. + /// + /// The priority to search for. + /// An indication of whether items were found having the specified priority. + protected virtual bool RemoveItems(TPriority priority) + { + LinkedList items; + + if (tree.TryGetValue(priority, out items)) + { + tree.Remove(priority); + Count -= items.Count; + return true; + } + + return false; + } + + /// + /// Removes the items with the specified priority. + /// + /// The priority. + /// The items with the specified priority. + public IList GetPriorityGroup(TPriority priority) + { + LinkedList items; + + return tree.TryGetValue(priority, out items) ? new List(items) : new List(); + } + + + /// + /// Adds the specified items to the priority queue with the specified priority. + /// + /// The items. + /// The priority. + /// is a null reference (Nothing in Visual Basic). + public void AddPriorityGroup(IList items, TPriority priority) + { + #region Validation + + Guard.ArgumentNotNull(items, "items"); + + #endregion + + AddPriorityGroupItem(items, priority); + } + + /// + /// Adds the specified items to the priority queue with the specified priority. + /// + /// The items. + /// The priority. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void AddPriorityGroupItem(IList items, TPriority priority) + { + LinkedList currentValues; + + if (tree.TryGetValue(priority, out currentValues)) + { + for (var i = 0; i < items.Count; i++) + { + currentValues.AddLast(items[i]); + } + } + else + { + currentValues = new LinkedList(items); + tree.Add(priority, currentValues); + } + } + + #endregion + + #region Protected Members + + /// + /// Adds the item to the queue. + /// + /// The item to add. + /// The priority of the item. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void AddItem(TValue item, TPriority priority) + { + LinkedList list; + + if (tree.TryGetValue(priority, out list)) + { + list.AddLast(item); + } + else + { + list = new LinkedList(); + list.AddLast(item); + tree.Add(priority, list); + } + + Count++; + } + + /// + /// Clears all the objects in this instance. + /// + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void ClearItems() + { + tree.Clear(); + Count = 0; + } + + #endregion + + #region Private Members + + /// + /// Checks if the list is not empty, and if it is, throw an exception. + /// + private void CheckTreeNotEmpty() + { + if (tree.Count == 0) + { + throw new InvalidOperationException("The Priority Queue is empty."); + } + } + + /// + /// Gets the next item. + /// + private KeyValuePair> GetNextItem() + { + #region Validation + + CheckTreeNotEmpty(); + + #endregion + + return queueType == PriorityQueueType.Maximum ? tree.Maximum : tree.Minimum; + } + + #endregion + } +} diff --git a/Perspex.Base/Threading/NGenerics/PriorityQueueType.cs b/Perspex.Base/Threading/NGenerics/PriorityQueueType.cs new file mode 100644 index 0000000000..4b23aa4696 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/PriorityQueueType.cs @@ -0,0 +1,27 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + +namespace NGenerics.DataStructures.Queues +{ + /// + /// Specifies the Priority Queue type (min or max). + /// + internal enum PriorityQueueType + { + /// + /// Specify a Minimum . + /// + Minimum = 0, + + /// + /// Specify a Maximum . + /// + Maximum = 1 + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTree.cs b/Perspex.Base/Threading/NGenerics/RedBlackTree.cs new file mode 100644 index 0000000000..d7e2bfb699 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/RedBlackTree.cs @@ -0,0 +1,247 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +/* + * The insertion and deletion code is based on the code found at http://eternallyconfuzzled.com/tuts/redblack.htm. + * It's an excellent tutorial - if you want to understand Red Black trees, look there first. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace NGenerics.DataStructures.Trees { + /// + /// An implementation of a Red-Black tree. + /// + /// The type of element to keep in the tree. + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + //[Serializable] + internal class RedBlackTree : BinarySearchTreeBase { + + #region Construction + + /// + public RedBlackTree() { + // Do nothing - the default Comparer will be used by the base class. + } + + + /// + public RedBlackTree(IComparer comparer) + : base(comparer) { + // Do nothing else. + } + + /// + public RedBlackTree(Comparison comparison) + : base(comparison) { + // Do nothing else. + } + + #endregion + + #region Public Members + + /// + /// Adds an element with the provided key and value to the . + /// + /// The item. + protected override void AddItem(T item) { + #region Validation + + if (Equals(item, null)) { + throw new ArgumentNullException("item"); + } + + #endregion + + var root = (RedBlackTreeNode)Tree; + + var newRoot = InsertNode(root, item); + newRoot.Color = NodeColor.Black; + Tree = newRoot; + } + + /// + /// Removes the element with the specified key from the . + /// + /// The item to remove. + /// + /// true if the element is successfully removed; otherwise, false. This method also returns false if key was not found in the original . + /// + /// + protected override bool RemoveItem(T item) { + if (Tree != null) { + var startNode = new RedBlackTreeNode(default(T)); + + var childNode = startNode; + startNode.Right = (RedBlackTreeNode)Tree; + + RedBlackTreeNode parent = null; + RedBlackTreeNode foundNode = null; + + var direction = true; + + while (childNode[direction] != null) { + var lastDirection = direction; + + var grandParent = parent; + parent = childNode; + childNode = childNode[direction]; + + var comparisonValue = Comparer.Compare(childNode.Data, item); + + if (comparisonValue == 0) { + foundNode = childNode; + } + + direction = comparisonValue < 0; + + if ((IsBlack(childNode)) && (IsBlack(childNode[direction]))) { + if (IsRed(childNode[!direction])) { + parent = parent[lastDirection] = SingleRotation(childNode, direction); + } else if (IsBlack(childNode[direction])) { + var sibling = parent[!lastDirection]; + + if (sibling != null) { + if ((IsBlack(sibling.Left)) && (IsBlack(sibling.Right))) { + parent.Color = NodeColor.Black; + sibling.Color = NodeColor.Red; + childNode.Color = NodeColor.Red; + } else { + var parentDirection = grandParent.Right == parent; + + if (IsRed(sibling[lastDirection])) { + grandParent[parentDirection] = DoubleRotation(parent, lastDirection); + } else if (IsRed(sibling[!lastDirection])) { + grandParent[parentDirection] = SingleRotation(parent, lastDirection); + } + + childNode.Color = grandParent[parentDirection].Color = NodeColor.Red; + grandParent[parentDirection].Left.Color = NodeColor.Black; + grandParent[parentDirection].Right.Color = NodeColor.Black; + } + } + } + } + } + + if (foundNode != null) { + foundNode.Data = childNode.Data; + parent[parent.Right == childNode] = childNode[childNode.Left == null]; + } + + Tree = startNode.Right; + + if (Tree != null) { + ((RedBlackTreeNode)Tree).Color = NodeColor.Black; + } + + if (foundNode != null) { + return true; + } + } + + return false; + } + + #endregion + + #region Private Members + + /// + /// Determines whether the specified node is red. + /// + /// The node. + /// + /// true if the specified node is red; otherwise, false. + /// + private static bool IsRed(RedBlackTreeNode node) { + return (node != null) && (node.Color == NodeColor.Red); + } + + /// + /// Determines whether the specified node is black. + /// + /// The node. + /// + /// true if the specified node is black; otherwise, false. + /// + private static bool IsBlack(RedBlackTreeNode node) { + return (node == null) || (node.Color == NodeColor.Black); + } + + /// + /// A recursive implementation of insertion of a node into the tree. + /// + /// The start node. + /// The item. + /// The node created in the insertion. + private RedBlackTreeNode InsertNode(RedBlackTreeNode node, T item) { + if (node == null) { + node = new RedBlackTreeNode(item); + } else if (Comparer.Compare(item, node.Data) != 0) { + var direction = Comparer.Compare(node.Data, item) < 0; + + node[direction] = InsertNode(node[direction], item); + + if (IsRed(node[direction])) { + if (IsRed(node[!direction])) { + node.Color = NodeColor.Red; + node.Left.Color = NodeColor.Black; + node.Right.Color = NodeColor.Black; + } else { + if (IsRed(node[direction][direction])) { + node = SingleRotation(node, !direction); + } else if (IsRed(node[direction][!direction])) { + node = DoubleRotation(node, !direction); + } + } + } + } else { + throw new ArgumentException(alreadyContainedInTheTree); + } + + return node; + } + + /// + /// Perform a single rotation on the node provided.. + /// + /// The node on which to focus the rotation. + /// The direction of the rotation. If direction is equal to true, a right rotation is performed. Other wise, a left rotation. + /// The new root of the cluster. + private static RedBlackTreeNode SingleRotation(RedBlackTreeNode node, bool direction) { + var childSibling = node[!direction]; + + node[!direction] = childSibling[direction]; + childSibling[direction] = node; + + node.Color = NodeColor.Red; + childSibling.Color = NodeColor.Black; + + return childSibling; + } + + /// + /// Perform a double rotation on the node provided.. + /// + /// The node on which to focus the rotation. + /// The direction of the rotation. If direction is equal to true, a right rotation is performed. Other wise, a left rotation. + /// The new root of the cluster. + private static RedBlackTreeNode DoubleRotation(RedBlackTreeNode node, bool direction) { + node[!direction] = SingleRotation(node[!direction], !direction); + return SingleRotation(node, direction); + } + + #endregion + } +} diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTreeDictionary.cs b/Perspex.Base/Threading/NGenerics/RedBlackTreeDictionary.cs new file mode 100644 index 0000000000..5333915862 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/RedBlackTreeDictionary.cs @@ -0,0 +1,243 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +/* + * The insertion and deletion code is based on the code found at http://eternallyconfuzzled.com/tuts/redblack.htm. + * It's an excellent tutorial - if you want to understand Red Black trees, look there first. + */ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using NGenerics.Comparers; +using NGenerics.Patterns.Visitor; + +namespace NGenerics.DataStructures.Trees +{ + /// + /// An implementation of a Red-Black tree. + /// + /// The type of the keys in the . + /// The type of the values in the . + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if (!SILVERLIGHT && !WINDOWSPHONE) + //[Serializable] +#endif + internal class RedBlackTree : RedBlackTree>, IDictionary // BinarySearchTreeBase + { + #region Construction + /// + public RedBlackTree() : base(new KeyValuePairComparer()) + { + // Do nothing - the default Comparer will be used by the base class. + } + + + /// + public RedBlackTree(IComparer comparer) + : base(new KeyValuePairComparer(comparer)) + { + // Do nothing else. + } + + /// + public RedBlackTree(Comparison comparison) + : base(new KeyValuePairComparer(comparison)) + { + // Do nothing else. + } + + #endregion + + #region Private Members + + private RedBlackTreeNode> FindNode(TKey key) + { + return base.FindNode(new KeyValuePair(key, default(TValue))) as RedBlackTreeNode>; + } + + private bool Contains(KeyValuePair item, bool checkValue) { + var node = FindNode(item); + + if ((node != null) && !checkValue) { + return true; + } + + return node != null && Equals(item.Value, node.Data.Value); + } + + #endregion + + #region IDictionary Members + + /// + /// Removes the element with the specified key from the . + /// + /// The key of the element to remove. + /// + /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original . + /// + /// + /// is null. + /// + /// + /// The is read-only. + /// + public bool Remove(TKey key) { + return Remove(new KeyValuePair(key, default(TValue))); + } + + /// + /// Adds an element with the provided key and value to the . + /// + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + /// + /// is null. + /// + /// + /// An element with the same key already exists in the . + /// + /// + /// The is read-only. + /// + public void Add(TKey key, TValue value) { + + if (Equals(key, null)) + { + throw new ArgumentNullException("key"); + } + + Add(new KeyValuePair(key, value)); + } + + /// + /// Determines whether the contains an element with the specified key. + /// + /// The key to locate in the . + /// + /// true if the contains an element with the key; otherwise, false. + /// + /// + /// is null. + /// + public bool ContainsKey(TKey key) { + return Contains(new KeyValuePair(key, default(TValue)), false); + } + + /// + /// Gets an containing the keys of the . + /// + /// + /// + /// + /// + /// + public ICollection Keys + { + get + { + // Get the keys in sorted order + var visitor = new KeyTrackingVisitor(); + var inOrderVisitor = new InOrderVisitor>(visitor); + DepthFirstTraversal(inOrderVisitor); + return new ReadOnlyCollection(visitor.TrackingList); + } + } + + + /// + /// + /// + /// + /// + public bool TryGetValue(TKey key, out TValue value) + { + var node = FindNode(new KeyValuePair(key, default(TValue))); + + if (node == null) + { + value = default(TValue); + return false; + } + + value = node.Data.Value; + return true; + } + + /// + /// Gets an containing the values in the . + /// + /// + /// + /// + /// + /// + public ICollection Values + { + get + { + var visitor = new ValueTrackingVisitor(); + var inOrderVisitor = new InOrderVisitor>(visitor); + + DepthFirstTraversal(inOrderVisitor); + + return new ReadOnlyCollection(visitor.TrackingList); + } + } + + + /// + /// Gets or sets the value with the specified key. + /// + /// The key of the item to set or get. + public TValue this[TKey key] + { + get + { + var node = FindNode(key); + + if (node == null) + { + throw new KeyNotFoundException("key"); + } + + return node.Data.Value; + } + set + { + var node = FindNode(key); + + if (node == null) + { + throw new KeyNotFoundException("key"); + } + + node.Data = new KeyValuePair(key, value); + } + } + + #endregion + + #region ICollection> Members + + /// + /// + /// + /// + /// + public override bool Contains(KeyValuePair item) { + return Contains(item, true); + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTreeList.cs b/Perspex.Base/Threading/NGenerics/RedBlackTreeList.cs new file mode 100644 index 0000000000..3f88cb1cad --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/RedBlackTreeList.cs @@ -0,0 +1,225 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace NGenerics.DataStructures.Trees +{ + /// + /// A RedBlack Tree list variant. Equivalent to where TValue is a . + /// + /// The type of the key. + /// The type of the value. + //[Serializable] + internal class RedBlackTreeList : RedBlackTree> + { + #region Delegates + + private delegate bool NodeAction(TKey key, LinkedList values); + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the class. + /// + /// + public RedBlackTreeList() { + // Do nothing else. + } + + /// + public RedBlackTreeList(IComparer comparer) + : base(comparer) { + // Do nothing else. + } + + /// + public RedBlackTreeList(Comparison comparison) + : base(comparison) { + // Do nothing else. + } + + #endregion + + #region Public Members + + /// + /// Determines whether the specified value contains value. + /// + /// The value. + /// + /// true if the specified value contains value; otherwise, false. + /// + public bool ContainsValue(TValue value) + { + return TraverseItems((key, list) => list.Contains(value)); + } + + /// + /// Gets the value enumerator. + /// + /// An enumerator to enumerate through the values contained in this instance. + public IEnumerator GetValueEnumerator() + { + var stack = new Stack>>>(); + + if (Tree != null) + { + stack.Push(Tree); + } + + while (stack.Count > 0) + { + var currentNode = stack.Pop(); + + var list = currentNode.Data.Value; + + foreach (var item in list) + { + yield return item; + } + + if (currentNode.Left != null) + { + stack.Push(currentNode.Left); + } + + if (currentNode.Right != null) + { + stack.Push(currentNode.Right); + } + } + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public IEnumerator> GetKeyEnumerator() + { + var stack = new Stack>>>(); + + if (Tree != null) + { + stack.Push(Tree); + } + + while (stack.Count > 0) + { + var currentNode = stack.Pop(); + + var list = currentNode.Data.Value; + + foreach (var item in list) + { + yield return new KeyValuePair(currentNode.Data.Key, item); + } + + if (currentNode.Left != null) + { + stack.Push(currentNode.Left); + } + + if (currentNode.Right != null) + { + stack.Push(currentNode.Right); + } + } + } + + /// + /// Removes the specified value. + /// + /// The value. + /// The key under which the item was found. + /// A value indicating whether the item was found or not. + public bool Remove(TValue value, out TKey key) + { + var foundKey = default(TKey); + + var ret = TraverseItems( + delegate(TKey itemKey, LinkedList list) { + if (list.Remove(value)) + { + if (list.Count == 0) + { + Remove(itemKey); + } + + foundKey = itemKey; + return true; + } + + return false; + } + ); + + key = foundKey; + return ret; + } + + #endregion + + #region Private Members + + /// + /// Traverses the items. + /// + /// A predicate that performs an action on the list, and indicates whether the enumeration of items should stop or not. + /// An indication of whether the enumeration was stopped prematurely. + private bool TraverseItems(NodeAction shouldStop) + { + #region Validation + + Debug.Assert(shouldStop != null); + + #endregion + + var stack = new Stack>>>(); + + if (Tree != null) + { + stack.Push(Tree); + } + + while (stack.Count > 0) + { + var currentNode = stack.Pop(); + + if (shouldStop(currentNode.Data.Key, currentNode.Data.Value)) + { + return true; + } + + if (currentNode.Left != null) + { + stack.Push(currentNode.Left); + } + + if (currentNode.Right != null) + { + stack.Push(currentNode.Right); + } + } + + return false; + } + + #endregion + } +} diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTreeNode.cs b/Perspex.Base/Threading/NGenerics/RedBlackTreeNode.cs new file mode 100644 index 0000000000..5823f85306 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/RedBlackTreeNode.cs @@ -0,0 +1,90 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + + +using System;using System.Diagnostics.CodeAnalysis; + +namespace NGenerics.DataStructures.Trees { + /// + /// A container class, used for the RedBlackTree. + /// + /// The type of element. + //[Serializable] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + internal class RedBlackTreeNode : BinaryTree { + + #region Construction + + /// + /// Initializes a new instance of the class. + /// + /// The data contained in this node. + internal RedBlackTreeNode(T data) + : base(data) { + Color = NodeColor.Red; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the color of the current node. + /// + /// The color of the node. + internal NodeColor Color { get; set; } + + /// + /// Gets or sets the with the specified direction. + /// + /// + internal RedBlackTreeNode this[bool direction] { + get { + return direction ? Right : Left; + } + set { + if (direction) { + Right = value; + } else { + Left = value; + } + } + } + + /// + /// Gets or sets the left subtree. + /// + /// The left subtree. + internal new RedBlackTreeNode Left { + get { + return (RedBlackTreeNode)base.Left; + } + set { + base.Left = value; + } + } + + /// + /// Gets or sets the right subtree. + /// + /// The right subtree. + internal new RedBlackTreeNode Right { + get { + return (RedBlackTreeNode)base.Right; + } + set { + base.Right = value; + } + } + + + #endregion + } +} diff --git a/Perspex.Base/Threading/NGenerics/TrackingVisitor.cs b/Perspex.Base/Threading/NGenerics/TrackingVisitor.cs new file mode 100644 index 0000000000..14f5e6f2b6 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/TrackingVisitor.cs @@ -0,0 +1,73 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System.Collections.Generic; + +namespace NGenerics.Patterns.Visitor +{ + /// + /// A visitor that tracks (stores) objects in the order they were visited. + /// Handy for demonstrating and testing different ordered visits implementations on + /// data structures. + /// + /// The type of objects to be visited. + internal sealed class TrackingVisitor : IVisitor + { + #region Globals + + private readonly List tracks; + + #endregion + + #region Construction + + + /// + public TrackingVisitor() + { + tracks = new List(); + } + + #endregion + + #region IVisitor Members + /// + public void Visit(T obj) + { + tracks.Add(obj); + } + + /// + public bool HasCompleted { + get + { + return false; + } + } + + #endregion + + #region Public Members + + /// + /// Gets the tracking list. + /// + /// The tracking list. + public IList TrackingList + { + get + { + return tracks; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/ValueTrackingVisitor.cs b/Perspex.Base/Threading/NGenerics/ValueTrackingVisitor.cs new file mode 100644 index 0000000000..d0f8f0eff9 --- /dev/null +++ b/Perspex.Base/Threading/NGenerics/ValueTrackingVisitor.cs @@ -0,0 +1,75 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System.Collections.Generic; + +namespace NGenerics.Patterns.Visitor +{ + /// + /// A visitor that tracks (stores) keys from KeyValuePairs in the order they were visited. + /// + /// The type of key of the KeyValuePair. + /// The type of value of the KeyValuePair. + internal sealed class ValueTrackingVisitor : IVisitor> + { + #region Globals + + private readonly List tracks; + + #endregion + + #region Construction + + + /// + public ValueTrackingVisitor() + { + tracks = new List(); + } + + #endregion + + #region Public Members + + /// + /// Gets the tracking list. + /// + /// The tracking list. + public IList TrackingList + { + get + { + return tracks; + } + } + + #endregion + + #region IVisitor> Members + + + /// + public void Visit(KeyValuePair obj) + { + tracks.Add(obj.Value); + } + + /// + public bool HasCompleted + { + get + { + return false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Perspex.sln b/Perspex.sln index 2e9becd51f..a51fdeac1c 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -133,7 +133,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {811A76CF-1CF6-440F-963B-BBE31BD72A82} = {B39A8919-9F95-48FE-AD7B-76E08B509888} - {E3A1060B-50D0-44E8-88B6-F44EF2E5BD72} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {3E908F67-5543-4879-A1DC-08EACE79B3CD} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {DABFD304-D6A4-4752-8123-C2CCF7AC7831} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {FB05AC90-89BA-4F2F-A924-F37875FB547C} = {1D577B69-F23B-4C4F-8605-97704DAC75A9} diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index f0b4ed11a9..34720898e5 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -101,6 +101,7 @@ namespace TestApplication Window window = new Window { + Title = "Perspex Test Application", Content = new Grid { RowDefinitions = new RowDefinitions diff --git a/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 9173df3821..6c550a914e 100644 --- a/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -459,6 +459,9 @@ namespace Perspex.Win32.Interop IntPtr hInstance, IntPtr lpParam); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SetWindowText(IntPtr hwnd, String lpString); + public struct MSG { public IntPtr hwnd; @@ -498,6 +501,6 @@ namespace Perspex.Win32.Interop public string lpszMenuName; public string lpszClassName; public IntPtr hIconSm; - } + } } } diff --git a/Windows/Perspex.Win32/Perspex.Win32.csproj b/Windows/Perspex.Win32/Perspex.Win32.csproj index 9a05bea322..34820c5520 100644 --- a/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -64,7 +64,6 @@ - diff --git a/Windows/Perspex.Win32/Threading/WindowsDispatcher.cs b/Windows/Perspex.Win32/Threading/WindowsDispatcher.cs deleted file mode 100644 index 9abce33504..0000000000 --- a/Windows/Perspex.Win32/Threading/WindowsDispatcher.cs +++ /dev/null @@ -1,371 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright 2013 MIT Licence. See licence.md for more information. -// -// ----------------------------------------------------------------------- - -namespace Perspex.Win32.Threading -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Security; - using System.Threading; - using Perspex.Threading; - using Perspex.Win32.Interop; - - [Flags] - internal enum Flags - { - ShutdownStarted = 1, - Shutdown = 2, - Disabled = 4 - } - - public sealed class WindowsDispatcher : Dispatcher - { - private const int TopPriority = (int)DispatcherPriority.Send; - - private static Dictionary dispatchers = new Dictionary(); - - private static object olock = new object(); - - private Thread baseThread; - - private PokableQueue[] priorityQueues = new PokableQueue[TopPriority + 1]; - - private Flags flags; - - private int queueBits; - - private WindowsDispatcher(Thread t) - { - this.baseThread = t; - - for (int i = 1; i <= (int)DispatcherPriority.Send; i++) - { - this.priorityQueues[i] = new PokableQueue(); - } - } - - public event EventHandler ShutdownStarted; - - public event EventHandler ShutdownFinished; - - public override DispatcherFrame CurrentFrame - { - get; - set; - } - - public Thread Thread - { - get { return this.baseThread; } - } - - public bool HasShutdownStarted - { - get { return (this.flags & Flags.ShutdownStarted) != 0; } - } - - public override bool HasShutdownFinished - { - get { return (this.flags & Flags.Shutdown) != 0; } - } - - [SecurityCritical] - public static void ExitAllFrames() - { - Dispatcher dis = CurrentDispatcher; - - for (DispatcherFrame frame = dis.CurrentFrame; frame != null; frame = frame.ParentFrame) - { - if (frame.ExitOnRequest) - { - frame.Continue = false; - } - else - { - break; - } - } - } - - public static WindowsDispatcher FromThread(Thread thread) - { - WindowsDispatcher dis; - - if (dispatchers.TryGetValue(thread, out dis)) - { - return dis; - } - - return null; - } - - public static WindowsDispatcher GetThreadDispatcher() - { - lock (olock) - { - Thread t = Thread.CurrentThread; - WindowsDispatcher dis = FromThread(t); - - if (dis != null) - { - return dis; - } - - dis = new WindowsDispatcher(t); - dispatchers[t] = dis; - return dis; - } - } - - public override DispatcherOperation BeginInvoke(Action method) - { - return this.BeginInvoke(DispatcherPriority.Normal, method); - } - - public override DispatcherOperation BeginInvoke(DispatcherPriority priority, Action method) - { - if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send) - { - throw new InvalidEnumArgumentException("priority"); - } - - if (priority == DispatcherPriority.Inactive) - { - throw new ArgumentException("priority can not be inactive", "priority"); - } - - if (method == null) - { - throw new ArgumentNullException("method"); - } - - DispatcherOperation op = new DispatcherOperation(this, priority, method); - this.Queue(priority, op); - return op; - } - - [SecurityCritical] - public void InvokeShutdown() - { - this.flags |= Flags.ShutdownStarted; - - UnmanagedMethods.PostMessage( - IntPtr.Zero, - (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, - IntPtr.Zero, - IntPtr.Zero); - } - - protected override void Reprioritize(DispatcherOperation op, DispatcherPriority oldpriority) - { - int oldp = (int)oldpriority; - PokableQueue q = this.priorityQueues[oldp]; - - lock (q) - { - q.Remove(op); - } - - this.Queue(op.Priority, op); - } - - private void Queue(DispatcherPriority priority, DispatcherOperation x) - { - int p = (int)priority; - PokableQueue q = this.priorityQueues[p]; - - lock (q) - { - int flag = 1 << p; - q.Enqueue(x); - this.queueBits |= flag; - } - - if (Thread.CurrentThread != this.baseThread) - { - UnmanagedMethods.PostMessage( - IntPtr.Zero, - (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, - IntPtr.Zero, - IntPtr.Zero); - } - } - - private void PerformShutdown() - { - EventHandler h; - - h = this.ShutdownStarted; - if (h != null) - { - h(this, new EventArgs()); - } - - this.flags |= Flags.Shutdown; - - h = this.ShutdownFinished; - if (h != null) - { - h(this, new EventArgs()); - } - - this.priorityQueues = null; - } - - protected override void RunFrame(DispatcherFrame frame) - { - do - { - while (this.queueBits != 0) - { - for (int i = TopPriority; i > 0 && this.queueBits != 0; i--) - { - int currentBit = this.queueBits & (1 << i); - if (currentBit != 0) - { - PokableQueue q = this.priorityQueues[i]; - - do - { - DispatcherOperation task; - - lock (q) - { - task = (DispatcherOperation)q.Dequeue(); - } - - task.Invoke(); - - if (!frame.Continue) - { - return; - } - - if (this.HasShutdownStarted) - { - this.PerformShutdown(); - return; - } - - lock (q) - { - if (q.Count == 0) - { - this.queueBits &= ~(1 << i); - break; - } - } - - if (currentBit < (this.queueBits & ~currentBit)) - { - break; - } - } - while (true); - } - } - } - - UnmanagedMethods.MSG msg; - UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); - UnmanagedMethods.TranslateMessage(ref msg); - UnmanagedMethods.DispatchMessage(ref msg); - - if (this.HasShutdownStarted) - { - this.PerformShutdown(); - return; - } - } - while (frame.Continue); - } - - private class PokableQueue - { - private const int InitialCapacity = 32; - - private int size, head, tail; - private object[] array; - - internal PokableQueue(int capacity) - { - this.array = new object[capacity]; - } - - internal PokableQueue() - : this(InitialCapacity) - { - } - - public int Count - { - get - { - return this.size; - } - } - - public void Enqueue(object obj) - { - if (this.size == this.array.Length) - { - this.Grow(); - } - - this.array[this.tail] = obj; - this.tail = (this.tail + 1) % this.array.Length; - this.size++; - } - - public object Dequeue() - { - if (this.size < 1) - { - throw new InvalidOperationException(); - } - - object result = this.array[this.head]; - this.array[this.head] = null; - this.head = (this.head + 1) % this.array.Length; - this.size--; - return result; - } - - public void Remove(object obj) - { - for (int i = 0; i < this.size; i++) - { - if (this.array[(this.head + i) % this.array.Length] == obj) - { - for (int j = i; j < this.size - i; j++) - { - this.array[(this.head + j) % this.array.Length] = this.array[(this.head + j + 1) % this.array.Length]; - } - - this.size--; - if (this.size < 0) - { - this.size = this.array.Length - 1; - } - - this.tail--; - } - } - } - - private void Grow() - { - int newc = this.array.Length * 2; - object[] newContents = new object[newc]; - this.array.CopyTo(newContents, 0); - this.array = newContents; - this.head = 0; - this.tail = this.head + this.size; - } - } - } -} \ No newline at end of file diff --git a/Windows/Perspex.Win32/Win32Platform.cs b/Windows/Perspex.Win32/Win32Platform.cs index 76d58bb07f..a0de44a705 100644 --- a/Windows/Perspex.Win32/Win32Platform.cs +++ b/Windows/Perspex.Win32/Win32Platform.cs @@ -7,22 +7,20 @@ namespace Perspex.Win32 { using System; - using System.Collections.Generic; + using System.Reactive.Disposables; + using System.Threading; + using System.Threading.Tasks; using Perspex.Input; using Perspex.Platform; using Perspex.Threading; using Perspex.Win32.Input; using Perspex.Win32.Interop; - using Perspex.Win32.Threading; using Splat; public class Win32Platform : IPlatformThreadingInterface { private static Win32Platform instance = new Win32Platform(); - private Dictionary timerCallbacks = - new Dictionary(); - public static void Initialize() { var locator = Locator.CurrentMutable; @@ -30,19 +28,15 @@ namespace Perspex.Win32 locator.Register(() => instance, typeof(IPlatformThreadingInterface)); } - public Dispatcher GetThreadDispatcher() - { - return WindowsDispatcher.GetThreadDispatcher(); - } - - - public void KillTimer(object handle) + public void ProcessMessage() { - this.timerCallbacks.Remove((IntPtr)handle); - UnmanagedMethods.KillTimer(IntPtr.Zero, (IntPtr)handle); + UnmanagedMethods.MSG msg; + UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); + UnmanagedMethods.TranslateMessage(ref msg); + UnmanagedMethods.DispatchMessage(ref msg); } - public object StartTimer(TimeSpan interval, Action callback) + public IDisposable StartTimer(TimeSpan interval, Action callback) { UnmanagedMethods.TimerProc timerDelegate = (UnmanagedMethods.TimerProc) ((hWnd, uMsg, nIDEvent, dwTime) => callback()); @@ -53,9 +47,19 @@ namespace Perspex.Win32 (uint)interval.TotalMilliseconds, timerDelegate); - this.timerCallbacks.Add(handle, timerDelegate); + return Disposable.Create(() => + { + UnmanagedMethods.KillTimer(IntPtr.Zero, handle); + }); + } - return handle; + public void Wake() + { + UnmanagedMethods.PostMessage( + IntPtr.Zero, + (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, + IntPtr.Zero, + IntPtr.Zero); } } } diff --git a/Windows/Perspex.Win32/Window.cs b/Windows/Perspex.Win32/Window.cs index 44cee12b42..f97479b273 100644 --- a/Windows/Perspex.Win32/Window.cs +++ b/Windows/Perspex.Win32/Window.cs @@ -22,15 +22,18 @@ namespace Perspex.Win32 using Perspex.Threading; using Perspex.Win32.Input; using Perspex.Win32.Interop; - using Perspex.Win32.Threading; using Splat; public class Window : ContentControl, ILayoutRoot, IRenderRoot, ICloseable { + public static readonly PerspexProperty TitleProperty = PerspexProperty.Register("Title"); + private UnmanagedMethods.WndProc wndProcDelegate; private string className; + private Dispatcher dispatcher; + private IRenderer renderer; private IInputManager inputManager; @@ -41,30 +44,32 @@ namespace Perspex.Win32 this.CreateWindow(); Size clientSize = this.ClientSize; + this.dispatcher = Dispatcher.UIThread; this.LayoutManager = new LayoutManager(this); this.RenderManager = new RenderManager(); this.renderer = factory.CreateRenderer(this.Handle, (int)clientSize.Width, (int)clientSize.Height); this.inputManager = Locator.Current.GetService(); this.Template = ControlTemplate.Create(this.DefaultTemplate); - this.LayoutManager.LayoutNeeded.Subscribe(x => + this.LayoutManager.LayoutNeeded.Subscribe(x => { - WindowsDispatcher.CurrentDispatcher.BeginInvoke( - DispatcherPriority.Render, + this.dispatcher.InvokeAsync( () => { this.LayoutManager.ExecuteLayoutPass(); this.renderer.Render(this); this.RenderManager.RenderFinished(); - }); + }, + DispatcherPriority.Render); }); + this.GetObservable(TitleProperty).Subscribe(s => UnmanagedMethods.SetWindowText(Handle, s)); + this.RenderManager.RenderNeeded .Where(_ => !this.LayoutManager.LayoutQueued) .Subscribe(x => { - WindowsDispatcher.CurrentDispatcher.BeginInvoke( - DispatcherPriority.Render, + this.dispatcher.InvokeAsync( () => { if (!this.LayoutManager.LayoutQueued) @@ -72,7 +77,8 @@ namespace Perspex.Win32 this.renderer.Render(this); this.RenderManager.RenderFinished(); } - }); + }, + DispatcherPriority.Render); }); } @@ -80,6 +86,12 @@ namespace Perspex.Win32 public event EventHandler Closed; + public string Title + { + get { return this.GetValue(TitleProperty); } + set { this.SetValue(TitleProperty, value); } + } + public Size ClientSize { get