2016-03-30 23:20:52 +02:00
|
|
|
/********************************************************************++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
--********************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Collections;
|
|
|
|
using System.Management.Automation;
|
|
|
|
using System.Management.Automation.Host;
|
|
|
|
using System.Management.Automation.Internal;
|
|
|
|
|
|
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Microsoft.PowerShell
|
|
|
|
{
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// Represents all of the outstanding progress activities received by the host, and includes methods to update that state
|
|
|
|
/// upon receipt of new ProgressRecords, and to render that state into an array of strings such that ProgressPane can
|
2016-03-30 23:20:52 +02:00
|
|
|
/// display it.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The set of activities that we're tracking is logically a binary tree, with siblings in one branch and children in
|
2017-01-16 22:31:14 +01:00
|
|
|
/// another. For ease of implementation, this tree is represented as lists of lists. We use ArrayList as out list type,
|
|
|
|
/// although List1 (generic List) would also have worked. I suspect that ArrayList is faster because there are fewer links
|
2016-03-30 23:20:52 +02:00
|
|
|
/// to twiddle, though I have not measured that.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// This class uses lots of nearly identical helper functions to recursively traverse the tree. If I weren't so pressed
|
2016-03-30 23:20:52 +02:00
|
|
|
/// for time, I would see if generic methods could be used to collapse the number of traversers.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
internal
|
|
|
|
class PendingProgress
|
|
|
|
{
|
|
|
|
#region Updating Code
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Update the data structures that represent the outstanding progress records reported so far.
|
|
|
|
///
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="sourceId">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// Identifier of the source of the event. This is used as part of the "key" for matching newly received records with
|
|
|
|
/// records that have already been received. For a record to match (meaning that they refer to the same activity), both
|
2016-03-30 23:20:52 +02:00
|
|
|
/// the source and activity identifiers need to match.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="record">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// The ProgressRecord received that will either update the status of an activity which we are already tracking, or
|
2016-03-30 23:20:52 +02:00
|
|
|
/// represent a new activity that we need to track.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
|
|
|
|
internal
|
|
|
|
void
|
|
|
|
Update(Int64 sourceId, ProgressRecord record)
|
|
|
|
{
|
|
|
|
Dbg.Assert(record != null, "record should not be null");
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (record.ParentActivityId == record.ActivityId)
|
|
|
|
{
|
|
|
|
// ignore malformed records.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ArrayList listWhereFound = null;
|
|
|
|
int indexWhereFound = -1;
|
|
|
|
ProgressNode foundNode =
|
|
|
|
FindNodeById(sourceId, record.ActivityId, out listWhereFound, out indexWhereFound);
|
|
|
|
|
|
|
|
if (foundNode != null)
|
|
|
|
{
|
|
|
|
Dbg.Assert(listWhereFound != null, "node found, but list not identified");
|
|
|
|
Dbg.Assert(indexWhereFound >= 0, "node found, but index not returned");
|
|
|
|
|
|
|
|
if (record.RecordType == ProgressRecordType.Completed)
|
|
|
|
{
|
|
|
|
RemoveNodeAndPromoteChildren(listWhereFound, indexWhereFound);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (record.ParentActivityId == foundNode.ParentActivityId)
|
|
|
|
{
|
2017-01-16 22:31:14 +01:00
|
|
|
// record is an update to an existing activity. Copy the record data into the found node, and
|
2016-03-30 23:20:52 +02:00
|
|
|
// reset the age of the node.
|
|
|
|
|
|
|
|
foundNode.Activity = record.Activity;
|
|
|
|
foundNode.StatusDescription = record.StatusDescription;
|
|
|
|
foundNode.CurrentOperation = record.CurrentOperation;
|
|
|
|
foundNode.PercentComplete = Math.Min(record.PercentComplete, 100);
|
|
|
|
foundNode.SecondsRemaining = record.SecondsRemaining;
|
|
|
|
foundNode.Age = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-01-16 22:31:14 +01:00
|
|
|
// The record's parent Id mismatches with that of the found node's. We interpret
|
2016-03-30 23:20:52 +02:00
|
|
|
// this to mean that the activity represented by the record (and the found node) is
|
|
|
|
// being "re-parented" elsewhere. So we remove the found node and treat the record
|
|
|
|
// as a new activity.
|
|
|
|
|
|
|
|
RemoveNodeAndPromoteChildren(listWhereFound, indexWhereFound);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, the record's activity is not in the tree. So we need to add it.
|
|
|
|
|
|
|
|
if (record.RecordType == ProgressRecordType.Completed)
|
|
|
|
{
|
|
|
|
// We don't track completion records that don't correspond to activities we're not
|
|
|
|
// already tracking.
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ProgressNode newNode = new ProgressNode(sourceId, record);
|
|
|
|
|
|
|
|
// If we're adding a node, and we have no more space, then we need to pick a node to evict.
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
while (_nodeCount >= maxNodeCount)
|
2016-03-30 23:20:52 +02:00
|
|
|
{
|
|
|
|
EvictNode();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newNode.ParentActivityId >= 0)
|
|
|
|
{
|
|
|
|
ProgressNode parentNode = FindNodeById(newNode.SourceId, newNode.ParentActivityId);
|
|
|
|
if (parentNode != null)
|
|
|
|
{
|
|
|
|
if (parentNode.Children == null)
|
|
|
|
{
|
|
|
|
parentNode.Children = new ArrayList();
|
|
|
|
}
|
|
|
|
|
|
|
|
AddNode(parentNode.Children, newNode);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The parent node is not in the tree. Make the new node's parent the root,
|
|
|
|
// and add it to the tree. If the parent ever shows up, then the next time
|
2017-01-16 22:31:14 +01:00
|
|
|
// we receive a record for this activity, the parent id's won't match, and the
|
2016-03-30 23:20:52 +02:00
|
|
|
// activity will be properly re-parented.
|
|
|
|
|
|
|
|
newNode.ParentActivityId = -1;
|
|
|
|
}
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
AddNode(_topLevelNodes, newNode);
|
2016-03-30 23:20:52 +02:00
|
|
|
} while (false);
|
|
|
|
|
|
|
|
// At this point the tree is up-to-date. Make a pass to age all of the nodes
|
|
|
|
|
|
|
|
AgeNodesAndResetStyle();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
void
|
|
|
|
EvictNode()
|
|
|
|
{
|
|
|
|
ArrayList listWhereFound = null;
|
|
|
|
int indexWhereFound = -1;
|
|
|
|
|
|
|
|
ProgressNode oldestNode = FindOldestLeafmostNode(out listWhereFound, out indexWhereFound);
|
|
|
|
if (oldestNode == null)
|
|
|
|
{
|
|
|
|
// Well that's a surprise. There's got to be at least one node there that's older than 0.
|
|
|
|
|
|
|
|
Dbg.Assert(false, "Must be an old node in the tree somewhere");
|
|
|
|
|
|
|
|
// We'll just pick the root node, then.
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
RemoveNode(_topLevelNodes, 0);
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
RemoveNode(listWhereFound, indexWhereFound);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Removes a node from the tree.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="nodes">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// List in the tree from which the node is to be removed.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="indexToRemove">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Index into the list of the node to be removed.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
private
|
|
|
|
void
|
|
|
|
RemoveNode(ArrayList nodes, int indexToRemove)
|
|
|
|
{
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
2016-07-29 22:02:49 +02:00
|
|
|
ProgressNode nodeToRemove = (ProgressNode)nodes[indexToRemove];
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
Dbg.Assert(nodes != null, "can't remove nodes from a null list");
|
|
|
|
Dbg.Assert(indexToRemove < nodes.Count, "index is not in list");
|
|
|
|
Dbg.Assert(nodes[indexToRemove] != null, "no node at specified index");
|
|
|
|
Dbg.Assert(nodeToRemove.Children == null || nodeToRemove.Children.Count == 0, "can't remove a node with children");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
nodes.RemoveAt(indexToRemove);
|
2016-07-29 22:02:49 +02:00
|
|
|
--_nodeCount;
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_ON
|
2016-07-29 22:02:49 +02:00
|
|
|
Dbg.Assert(_nodeCount == this.CountNodes(), "We've lost track of the number of nodes in the tree");
|
2016-03-30 23:20:52 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
void
|
|
|
|
RemoveNodeAndPromoteChildren(ArrayList nodes, int indexToRemove)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
ProgressNode nodeToRemove = (ProgressNode)nodes[indexToRemove];
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
Dbg.Assert(nodes != null, "can't remove nodes from a null list");
|
|
|
|
Dbg.Assert(indexToRemove < nodes.Count, "index is not in list");
|
|
|
|
Dbg.Assert(nodeToRemove != null, "no node at specified index");
|
|
|
|
|
|
|
|
if (nodeToRemove == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nodeToRemove.Children != null)
|
|
|
|
{
|
|
|
|
// promote the children.
|
|
|
|
|
|
|
|
for (int i = 0; i < nodeToRemove.Children.Count; ++i)
|
|
|
|
{
|
|
|
|
// unparent the children. If the children are ever updated again, they will be reparented.
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
((ProgressNode)nodeToRemove.Children[i]).ParentActivityId = -1;
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
2017-01-16 22:31:14 +01:00
|
|
|
// add the children as siblings
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
nodes.RemoveAt(indexToRemove);
|
2016-07-29 22:02:49 +02:00
|
|
|
--_nodeCount;
|
2016-03-30 23:20:52 +02:00
|
|
|
nodes.InsertRange(indexToRemove, nodeToRemove.Children);
|
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
2016-07-29 22:02:49 +02:00
|
|
|
Dbg.Assert(_nodeCount == this.CountNodes(), "We've lost track of the number of nodes in the tree");
|
2016-03-30 23:20:52 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// nothing to promote
|
|
|
|
|
|
|
|
RemoveNode(nodes, indexToRemove);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Adds a node to the tree, first removing the oldest node if the tree is too large.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="nodes">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// List in the tree where the node is to be added.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="nodeToAdd">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Node to be added.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
|
|
|
|
private
|
|
|
|
void
|
|
|
|
AddNode(ArrayList nodes, ProgressNode nodeToAdd)
|
|
|
|
{
|
|
|
|
nodes.Add(nodeToAdd);
|
2016-07-29 22:02:49 +02:00
|
|
|
++_nodeCount;
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
2016-07-29 22:02:49 +02:00
|
|
|
Dbg.Assert(_nodeCount == this.CountNodes(), "We've lost track of the number of nodes in the tree");
|
|
|
|
Dbg.Assert(_nodeCount <= maxNodeCount, "Too many nodes in tree!");
|
2016-03-30 23:20:52 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
class FindOldestNodeVisitor : NodeVisitor
|
|
|
|
{
|
|
|
|
internal override
|
|
|
|
bool
|
|
|
|
Visit(ProgressNode node, ArrayList listWhereFound, int indexWhereFound)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
if (node.Age >= _oldestSoFar)
|
2016-03-30 23:20:52 +02:00
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
_oldestSoFar = node.Age;
|
2016-03-30 23:20:52 +02:00
|
|
|
FoundNode = node;
|
|
|
|
this.ListWhereFound = listWhereFound;
|
|
|
|
this.IndexWhereFound = indexWhereFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal
|
|
|
|
ProgressNode
|
|
|
|
FoundNode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal
|
|
|
|
ArrayList
|
|
|
|
ListWhereFound;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal
|
|
|
|
int
|
|
|
|
IndexWhereFound = -1;
|
|
|
|
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private int _oldestSoFar;
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
ProgressNode
|
|
|
|
FindOldestLeafmostNodeHelper(ArrayList treeToSearch, out ArrayList listWhereFound, out int indexWhereFound)
|
|
|
|
{
|
|
|
|
listWhereFound = null;
|
|
|
|
indexWhereFound = -1;
|
|
|
|
|
|
|
|
FindOldestNodeVisitor v = new FindOldestNodeVisitor();
|
|
|
|
NodeVisitor.VisitNodes(treeToSearch, v);
|
|
|
|
|
|
|
|
listWhereFound = v.ListWhereFound;
|
|
|
|
indexWhereFound = v.IndexWhereFound;
|
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
|
|
|
if (v.FoundNode == null)
|
|
|
|
{
|
|
|
|
Dbg.Assert(listWhereFound == null, "list should be null when no node found");
|
|
|
|
Dbg.Assert(indexWhereFound == -1, "index should indicate no node found");
|
2016-07-29 22:02:49 +02:00
|
|
|
Dbg.Assert(_topLevelNodes.Count == 0, "if there is no oldest node, then the tree must be empty");
|
|
|
|
Dbg.Assert(_nodeCount == 0, "if there is no oldest node, then the tree must be empty");
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return v.FoundNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
ProgressNode
|
|
|
|
FindOldestLeafmostNode(out ArrayList listWhereFound, out int indexWhereFound)
|
|
|
|
{
|
|
|
|
listWhereFound = null;
|
|
|
|
indexWhereFound = -1;
|
|
|
|
|
|
|
|
ProgressNode result = null;
|
2016-07-29 22:02:49 +02:00
|
|
|
ArrayList treeToSearch = _topLevelNodes;
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
result = FindOldestLeafmostNodeHelper(treeToSearch, out listWhereFound, out indexWhereFound);
|
|
|
|
if (result == null || result.Children == null || result.Children.Count == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// search the subtree for the oldest child
|
|
|
|
|
|
|
|
treeToSearch = result.Children;
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Convenience overload.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
private
|
|
|
|
ProgressNode
|
|
|
|
FindNodeById(Int64 sourceId, int activityId)
|
|
|
|
{
|
|
|
|
ArrayList listWhereFound = null;
|
|
|
|
int indexWhereFound = -1;
|
|
|
|
return
|
|
|
|
FindNodeById(sourceId, activityId, out listWhereFound, out indexWhereFound);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
class FindByIdNodeVisitor : NodeVisitor
|
|
|
|
{
|
|
|
|
internal
|
|
|
|
FindByIdNodeVisitor(Int64 sourceIdToFind, int activityIdToFind)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
_sourceIdToFind = sourceIdToFind;
|
|
|
|
_idToFind = activityIdToFind;
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal override
|
|
|
|
bool
|
|
|
|
Visit(ProgressNode node, ArrayList listWhereFound, int indexWhereFound)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
if (node.ActivityId == _idToFind && node.SourceId == _sourceIdToFind)
|
2016-03-30 23:20:52 +02:00
|
|
|
{
|
|
|
|
this.FoundNode = node;
|
|
|
|
this.ListWhereFound = listWhereFound;
|
|
|
|
this.IndexWhereFound = indexWhereFound;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal
|
|
|
|
ProgressNode
|
|
|
|
FoundNode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal
|
|
|
|
ArrayList
|
|
|
|
ListWhereFound;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal
|
|
|
|
int
|
|
|
|
IndexWhereFound = -1;
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private int _idToFind = -1;
|
|
|
|
private Int64 _sourceIdToFind;
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Finds a node with a given ActivityId in provided set of nodes. Recursively walks the set of nodes and their children.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="sourceId">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Identifier of the source of the record.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="activityId">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// ActivityId to search for.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="listWhereFound">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Receives reference to the List where the found node was located, or null if no suitable node was found.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="indexWhereFound">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// Receives the index into listWhereFound that indicating where in the list the node was located, or -1 if
|
2016-03-30 23:20:52 +02:00
|
|
|
/// no suitable node was found.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The found node, or null if no suitable node was located.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
|
|
|
|
|
|
|
private
|
|
|
|
ProgressNode
|
|
|
|
FindNodeById(Int64 sourceId, int activityId, out ArrayList listWhereFound, out int indexWhereFound)
|
|
|
|
{
|
|
|
|
listWhereFound = null;
|
|
|
|
indexWhereFound = -1;
|
|
|
|
|
|
|
|
FindByIdNodeVisitor v = new FindByIdNodeVisitor(sourceId, activityId);
|
2016-07-29 22:02:49 +02:00
|
|
|
NodeVisitor.VisitNodes(_topLevelNodes, v);
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
listWhereFound = v.ListWhereFound;
|
|
|
|
indexWhereFound = v.IndexWhereFound;
|
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
|
|
|
if (v.FoundNode == null)
|
|
|
|
{
|
|
|
|
Dbg.Assert(listWhereFound == null, "list should be null when no node found");
|
|
|
|
Dbg.Assert(indexWhereFound == -1, "index should indicate no node found");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return v.FoundNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Finds the oldest node with a given rendering style that is at least as old as a given age.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="nodes">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// List of nodes to search. Child lists of each node in this list will also be searched.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="oldestSoFar"></param>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The minimum age of the node to be located. To find the oldest node, pass 0.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// <param name="style">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The rendering style of the node to be located.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The found node, or null if no suitable node was located.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
|
|
|
|
|
|
|
private
|
|
|
|
ProgressNode
|
|
|
|
FindOldestNodeOfGivenStyle(ArrayList nodes, int oldestSoFar, ProgressNode.RenderStyle style)
|
|
|
|
{
|
|
|
|
if (nodes == null)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
ProgressNode found = null;
|
|
|
|
for (int i = 0; i < nodes.Count; ++i)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
ProgressNode node = (ProgressNode)nodes[i];
|
2016-03-30 23:20:52 +02:00
|
|
|
Dbg.Assert(node != null, "nodes should not contain null elements");
|
|
|
|
|
|
|
|
if (node.Age >= oldestSoFar && node.Style == style)
|
|
|
|
{
|
|
|
|
found = node;
|
|
|
|
oldestSoFar = found.Age;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.Children != null)
|
|
|
|
{
|
|
|
|
ProgressNode child = FindOldestNodeOfGivenStyle(node.Children, oldestSoFar, style);
|
|
|
|
|
|
|
|
if (child != null)
|
|
|
|
{
|
|
|
|
// In this universe, parents can be younger than their children. We found a child older than us.
|
|
|
|
|
|
|
|
found = child;
|
|
|
|
oldestSoFar = found.Age;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
|
|
|
if (found != null)
|
|
|
|
{
|
|
|
|
Dbg.Assert(found.Style == style, "unexpected style");
|
|
|
|
Dbg.Assert(found.Age >= oldestSoFar, "unexpected age");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private
|
2016-03-30 23:20:52 +02:00
|
|
|
class AgeAndResetStyleVisitor : NodeVisitor
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
internal override
|
2016-03-30 23:20:52 +02:00
|
|
|
bool
|
|
|
|
Visit(ProgressNode node, ArrayList unused, int unusedToo)
|
|
|
|
{
|
|
|
|
node.Age = Math.Min(node.Age + 1, Int32.MaxValue - 1);
|
|
|
|
node.Style = ProgressNode.RenderStyle.FullPlus;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// Increments the age of each of the nodes in the given list, and all their children. Also sets the rendering
|
2016-03-30 23:20:52 +02:00
|
|
|
/// style of each node to "full."
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// All nodes are aged every time a new ProgressRecord is received.
|
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
private
|
|
|
|
void
|
|
|
|
AgeNodesAndResetStyle()
|
|
|
|
{
|
|
|
|
AgeAndResetStyleVisitor arsv = new AgeAndResetStyleVisitor();
|
2016-07-29 22:02:49 +02:00
|
|
|
NodeVisitor.VisitNodes(_topLevelNodes, arsv);
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion // Updating Code
|
|
|
|
|
|
|
|
#region Rendering Code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-08-26 22:46:03 +02:00
|
|
|
/// Generates an array of strings representing as much of the outstanding progress activities as possible within the given
|
2016-03-30 23:20:52 +02:00
|
|
|
/// space. As more outstanding activities are collected, nodes are "compressed" (i.e. rendered in an increasing terse
|
2017-01-16 22:31:14 +01:00
|
|
|
/// fashion) in order to display as many as possible. Ultimately, some nodes may be compressed to the point of
|
2016-03-30 23:20:52 +02:00
|
|
|
/// invisibility. The oldest nodes are compressed first.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="maxWidth">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum width (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="maxHeight">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum height (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="rawUI">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The PSHostRawUserInterface used to gauge string widths in the rendering.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// An array of strings containing the textual representation of the outstanding progress activities.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
internal
|
|
|
|
string[]
|
|
|
|
Render(int maxWidth, int maxHeight, PSHostRawUserInterface rawUI)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
Dbg.Assert(_topLevelNodes != null, "Shouldn't need to render progress if no data exists");
|
2016-03-30 23:20:52 +02:00
|
|
|
Dbg.Assert(maxWidth > 0, "maxWidth is too small");
|
|
|
|
Dbg.Assert(maxHeight >= 3, "maxHeight is too small");
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
if (_topLevelNodes == null || _topLevelNodes.Count <= 0)
|
2016-03-30 23:20:52 +02:00
|
|
|
{
|
|
|
|
// we have nothing to render.
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
int invisible = 0;
|
|
|
|
if (TallyHeight(rawUI, maxHeight, maxWidth) > maxHeight)
|
|
|
|
{
|
2017-01-16 22:31:14 +01:00
|
|
|
// This will smash down nodes until the tree will fit into the alloted number of lines. If in the
|
2016-03-30 23:20:52 +02:00
|
|
|
// process some nodes were made invisible, we will add a line to the display to say so.
|
|
|
|
|
|
|
|
invisible = CompressToFit(rawUI, maxHeight, maxWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
ArrayList result = new ArrayList();
|
2017-01-26 03:19:08 +01:00
|
|
|
string border = StringUtil.Padding(maxWidth);
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
result.Add(border);
|
2016-07-29 22:02:49 +02:00
|
|
|
RenderHelper(result, _topLevelNodes, 0, maxWidth, rawUI);
|
2016-03-30 23:20:52 +02:00
|
|
|
if (invisible == 1)
|
|
|
|
{
|
|
|
|
result.Add(
|
|
|
|
" "
|
|
|
|
+ StringUtil.Format(
|
|
|
|
ProgressNodeStrings.InvisibleNodesMessageSingular,
|
|
|
|
invisible));
|
|
|
|
}
|
|
|
|
else if (invisible > 1)
|
|
|
|
{
|
|
|
|
result.Add(
|
|
|
|
" "
|
|
|
|
+ StringUtil.Format(
|
|
|
|
ProgressNodeStrings.InvisibleNodesMessagePlural,
|
|
|
|
invisible));
|
|
|
|
}
|
|
|
|
|
|
|
|
result.Add(border);
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
return (string[])result.ToArray(typeof(string));
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Helper function for Render(). Recursively renders nodes.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="strings">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The rendered strings so far. Additional rendering will be appended.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="nodes">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The nodes to be rendered. All child nodes will also be rendered.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="indentation">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The current indentation level (in BufferCells).
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="maxWidth">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum number of BufferCells that the rendering can consume, horizontally.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="rawUI">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The PSHostRawUserInterface used to gauge string widths in the rendering.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
private
|
|
|
|
void
|
|
|
|
RenderHelper(ArrayList strings, ArrayList nodes, int indentation, int maxWidth, PSHostRawUserInterface rawUI)
|
|
|
|
{
|
|
|
|
Dbg.Assert(strings != null, "strings should not be null");
|
|
|
|
Dbg.Assert(nodes != null, "nodes should not be null");
|
|
|
|
|
|
|
|
if (nodes == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (ProgressNode node in nodes)
|
|
|
|
{
|
|
|
|
int lines = strings.Count;
|
|
|
|
|
|
|
|
node.Render(strings, indentation, maxWidth, rawUI);
|
|
|
|
|
|
|
|
if (node.Children != null)
|
|
|
|
{
|
|
|
|
// indent only if the rendering of node actually added lines to the strings.
|
|
|
|
|
|
|
|
int indentationIncrement = (strings.Count > lines) ? 2 : 0;
|
|
|
|
|
|
|
|
RenderHelper(strings, node.Children, indentation + indentationIncrement, maxWidth, rawUI);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private
|
2016-03-30 23:20:52 +02:00
|
|
|
class HeightTallyer : NodeVisitor
|
|
|
|
{
|
|
|
|
internal HeightTallyer(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
_rawUi = rawUi;
|
|
|
|
_maxHeight = maxHeight;
|
|
|
|
_maxWidth = maxWidth;
|
2016-03-30 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
internal override
|
2016-03-30 23:20:52 +02:00
|
|
|
bool
|
|
|
|
Visit(ProgressNode node, ArrayList unused, int unusedToo)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
Tally += node.LinesRequiredMethod(_rawUi, _maxWidth);
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
// We don't need to walk all the nodes, once it's larger than the max height, we should stop
|
2016-07-29 22:02:49 +02:00
|
|
|
if (Tally > _maxHeight)
|
2016-03-30 23:20:52 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private PSHostRawUserInterface _rawUi;
|
|
|
|
private int _maxHeight;
|
|
|
|
private int _maxWidth;
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
internal int Tally;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// Tallies up the number of BufferCells vertically that will be required to show all the ProgressNodes in the given
|
2016-03-30 23:20:52 +02:00
|
|
|
/// list, and all of their children.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="maxHeight">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum height (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="rawUi">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The PSHostRawUserInterface used to gauge string widths in the rendering.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The vertical height (in BufferCells) that will be required to show all of the nodes in the given list.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
|
|
|
/// <param name="maxWidth">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
///
|
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
|
|
|
|
private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth)
|
|
|
|
{
|
|
|
|
HeightTallyer ht = new HeightTallyer(rawUi, maxHeight, maxWidth);
|
2016-07-29 22:02:49 +02:00
|
|
|
NodeVisitor.VisitNodes(_topLevelNodes, ht);
|
2016-03-30 23:20:52 +02:00
|
|
|
return ht.Tally;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Debugging code. Verifies that all of the nodes in the given list have the given style.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="nodes"></param>
|
|
|
|
/// <param name="style"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
|
|
private
|
|
|
|
bool
|
|
|
|
AllNodesHaveGivenStyle(ArrayList nodes, ProgressNode.RenderStyle style)
|
|
|
|
{
|
|
|
|
if (nodes == null)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < nodes.Count; ++i)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
ProgressNode node = (ProgressNode)nodes[i];
|
2016-03-30 23:20:52 +02:00
|
|
|
Dbg.Assert(node != null, "nodes should not contain null elements");
|
|
|
|
|
|
|
|
if (node.Style != style)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (node.Children != null)
|
|
|
|
{
|
|
|
|
if (!AllNodesHaveGivenStyle(node.Children, style))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Debugging code. NodeVisitor that counts up the number of nodes in the tree.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private
|
|
|
|
class
|
|
|
|
CountingNodeVisitor : NodeVisitor
|
|
|
|
{
|
|
|
|
internal override
|
|
|
|
bool
|
|
|
|
Visit(ProgressNode unused, ArrayList unusedToo, int unusedThree)
|
|
|
|
{
|
|
|
|
++Count;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal
|
|
|
|
int
|
|
|
|
Count;
|
|
|
|
}
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
|
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Debugging code. Counts the number of nodes in the tree of nodes.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The number of nodes in the tree.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private
|
2016-03-30 23:20:52 +02:00
|
|
|
int
|
|
|
|
CountNodes()
|
|
|
|
{
|
|
|
|
CountingNodeVisitor cnv = new CountingNodeVisitor();
|
2016-07-29 22:02:49 +02:00
|
|
|
NodeVisitor.VisitNodes(_topLevelNodes, cnv);
|
2016-03-30 23:20:52 +02:00
|
|
|
return cnv.Count;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Helper function to CompressToFit. Considers compressing nodes from one level to another.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="rawUi">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The PSHostRawUserInterface used to gauge string widths in the rendering.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="maxHeight">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum height (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="maxWidth">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum width (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="nodesCompressed">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// Receives the number of nodes that were compressed. If the result of the method is false, then this will be the total
|
2016-03-30 23:20:52 +02:00
|
|
|
/// number of nodes being tracked (i.e. all of them will have been compressed).
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="priorStyle">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The rendering style (e.g. "compression level") that the nodes are expected to currently have.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="newStyle">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// The new rendering style that a node will have when it is compressed. If the result of the method is false, then all
|
2016-03-30 23:20:52 +02:00
|
|
|
/// nodes will have this rendering style.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// true to indicate that the nodes are compressed to the point that their rendering will fit within the constraint, or
|
2017-01-16 22:31:14 +01:00
|
|
|
/// false to indicate that all of the nodes are compressed to a given level, but that the rendering still can't fit
|
2016-03-30 23:20:52 +02:00
|
|
|
/// within the constraint.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
|
|
|
|
|
|
|
private
|
|
|
|
bool
|
|
|
|
CompressToFitHelper(
|
|
|
|
PSHostRawUserInterface rawUi,
|
|
|
|
int maxHeight,
|
|
|
|
int maxWidth,
|
|
|
|
out int nodesCompressed,
|
|
|
|
ProgressNode.RenderStyle priorStyle,
|
|
|
|
ProgressNode.RenderStyle newStyle)
|
|
|
|
{
|
|
|
|
nodesCompressed = 0;
|
|
|
|
|
|
|
|
int age = 0;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
ProgressNode node = FindOldestNodeOfGivenStyle(_topLevelNodes, age, priorStyle);
|
2016-03-30 23:20:52 +02:00
|
|
|
if (node == null)
|
|
|
|
{
|
|
|
|
// We've compressed every node of the prior style already.
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
node.Style = newStyle;
|
|
|
|
++nodesCompressed;
|
|
|
|
if (TallyHeight(rawUi, maxHeight, maxWidth) <= maxHeight)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
// If we get all the way to here, then we've compressed all the nodes and we still don't fit.
|
|
|
|
|
|
|
|
#if DEBUG || ASSERTIONS_TRACE
|
|
|
|
|
|
|
|
Dbg.Assert(
|
2016-07-29 22:02:49 +02:00
|
|
|
nodesCompressed == CountNodes(),
|
2016-03-30 23:20:52 +02:00
|
|
|
"We should have compressed every node in the tree.");
|
|
|
|
Dbg.Assert(
|
2016-07-29 22:02:49 +02:00
|
|
|
AllNodesHaveGivenStyle(_topLevelNodes, newStyle),
|
2016-03-30 23:20:52 +02:00
|
|
|
"We should have compressed every node in the tree.");
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
|
|
|
/// "Compresses" the nodes representing the outstanding progress activities until their rendering will fit within a
|
2016-03-30 23:20:52 +02:00
|
|
|
/// "given height, or until they are compressed to a given level. The oldest nodes are compressed first.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// This is a 4-stage process -- from least compressed to "invisible". At each stage we find the oldest nodes in the
|
|
|
|
/// tree and change their rendering style to a more compact style. As soon as the rendering of the nodes will fit within
|
2017-01-16 22:31:14 +01:00
|
|
|
/// the maxHeight, we stop. The result is that the most recent nodes will be the least compressed, the idea being that
|
2016-03-30 23:20:52 +02:00
|
|
|
/// the rendering should show the most recently updated activities with the most complete rendering for them possible.
|
|
|
|
///
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="rawUi">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The PSHostRawUserInterface used to gauge string widths in the rendering.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="maxHeight">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum height (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="maxWidth">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The maximum width (in BufferCells) that the rendering may consume.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The number of nodes that were made invisible during the compression.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
///</returns>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
private
|
|
|
|
int
|
|
|
|
CompressToFit(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth)
|
|
|
|
{
|
2016-07-29 22:02:49 +02:00
|
|
|
Dbg.Assert(_topLevelNodes != null, "Shouldn't need to compress if no data exists");
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
int nodesCompressed = 0;
|
|
|
|
|
2017-01-16 22:31:14 +01:00
|
|
|
// This algorithm potentially makes many, many passes over the tree. It might be possible to optimize
|
2016-03-30 23:20:52 +02:00
|
|
|
// that some, but I'm not trying to be too clever just yet.
|
|
|
|
|
|
|
|
if (
|
|
|
|
CompressToFitHelper(
|
|
|
|
rawUi,
|
|
|
|
maxHeight,
|
|
|
|
maxWidth,
|
|
|
|
out nodesCompressed,
|
|
|
|
ProgressNode.RenderStyle.FullPlus,
|
|
|
|
ProgressNode.RenderStyle.Full))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
CompressToFitHelper(
|
|
|
|
rawUi,
|
2016-07-29 22:02:49 +02:00
|
|
|
maxHeight,
|
2016-03-30 23:20:52 +02:00
|
|
|
maxWidth,
|
2016-07-29 22:02:49 +02:00
|
|
|
out nodesCompressed,
|
|
|
|
ProgressNode.RenderStyle.Full,
|
2016-03-30 23:20:52 +02:00
|
|
|
ProgressNode.RenderStyle.Compact))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
CompressToFitHelper(
|
|
|
|
rawUi,
|
2016-07-29 22:02:49 +02:00
|
|
|
maxHeight,
|
2016-03-30 23:20:52 +02:00
|
|
|
maxWidth,
|
2016-07-29 22:02:49 +02:00
|
|
|
out nodesCompressed,
|
|
|
|
ProgressNode.RenderStyle.Compact,
|
2016-03-30 23:20:52 +02:00
|
|
|
ProgressNode.RenderStyle.Minimal))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
CompressToFitHelper(
|
|
|
|
rawUi,
|
2016-07-29 22:02:49 +02:00
|
|
|
maxHeight,
|
2016-03-30 23:20:52 +02:00
|
|
|
maxWidth,
|
2016-07-29 22:02:49 +02:00
|
|
|
out nodesCompressed,
|
|
|
|
ProgressNode.RenderStyle.Minimal,
|
2016-03-30 23:20:52 +02:00
|
|
|
ProgressNode.RenderStyle.Invisible))
|
|
|
|
{
|
|
|
|
// The nodes that we compressed here are now invisible.
|
|
|
|
|
|
|
|
return nodesCompressed;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dbg.Assert(false, "with all nodes invisible, we should never reach this point.");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion // Rendering Code
|
|
|
|
|
|
|
|
#region Utility Code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private abstract
|
|
|
|
class NodeVisitor
|
|
|
|
{
|
|
|
|
/// <summary>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// Called for each node in the tree.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="node">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The node being visited.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="listWhereFound">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The list in which the node resides.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <param name="indexWhereFound">
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// The index into listWhereFound of the node.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// true to continue visiting nodes, false if not.
|
2017-01-16 22:31:14 +01:00
|
|
|
///
|
2016-03-30 23:20:52 +02:00
|
|
|
/// </returns>
|
2016-07-29 22:02:49 +02:00
|
|
|
|
2016-03-30 23:20:52 +02:00
|
|
|
internal abstract
|
|
|
|
bool
|
|
|
|
Visit(ProgressNode node, ArrayList listWhereFound, int indexWhereFound);
|
|
|
|
|
|
|
|
internal static
|
|
|
|
void
|
|
|
|
VisitNodes(ArrayList nodes, NodeVisitor v)
|
|
|
|
{
|
|
|
|
if (nodes == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < nodes.Count; ++i)
|
|
|
|
{
|
|
|
|
ProgressNode node = (ProgressNode)nodes[i];
|
|
|
|
Dbg.Assert(node != null, "nodes should not contain null elements");
|
|
|
|
|
|
|
|
if (!v.Visit(node, nodes, i))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.Children != null)
|
|
|
|
{
|
|
|
|
VisitNodes(node.Children, v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-29 22:02:49 +02:00
|
|
|
private ArrayList _topLevelNodes = new ArrayList();
|
|
|
|
private int _nodeCount;
|
2016-03-30 23:20:52 +02:00
|
|
|
private const int maxNodeCount = 128;
|
|
|
|
}
|
2017-01-16 22:31:14 +01:00
|
|
|
} // namespace
|
2016-03-30 23:20:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
|