diff --git a/UserClass/IProject.cs b/UserClass/IProject.cs
new file mode 100644
index 0000000..a6a4b8e
--- /dev/null
+++ b/UserClass/IProject.cs
@@ -0,0 +1,345 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace SunlightCentralizedControlManagement_SCCM_.UserClass
+{
+ ///
+ /// Passive data class holding schedule information
+ ///
+ [Serializable]
+ public class IP_Task
+ {
+ ///
+ /// Initialize a new task to hold schedule information
+ ///
+ public IP_Task()
+ {
+ Complete = 0.0f;
+ Start = TimeSpan.Zero;
+ End = new TimeSpan(1, 0, 0, 0);
+ Duration = new TimeSpan(1, 0, 0, 0);
+ Slack = TimeSpan.Zero;
+ }
+
+ ///
+ /// Get or set the Name of this Task
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Indicate whether this task is collapsed such that sub tasks are hidden from view. Only groups can be collasped.
+ ///
+ public bool IsCollapsed { get; set; }
+
+ ///
+ /// Get or set the pecentage complete of this task, expressed in float between 0.0 and 1.0f.
+ ///
+ public float Complete { get; internal set; }
+
+ ///
+ /// Get the start time of this Task relative to the project start
+ ///
+ public TimeSpan Start { get; internal set; }
+
+ ///
+ /// Get the end time of this Task relative to the project start
+ ///
+ public TimeSpan End { get; internal set; }
+
+ ///
+ /// Get the duration of this Task in days
+ ///
+ public TimeSpan Duration { get; internal set; }
+
+ ///
+ /// Get the amount of slack (free float)
+ ///
+ public TimeSpan Slack { get; internal set; }
+
+ ///
+ /// Convert this Task to a descriptive string
+ ///
+ ///
+ public override string ToString()
+ {
+ return string.Format("[Name = {0}, Start = {1}, End = {2}, Duration = {3}, Complete = {4}]", Name, Start, End, Duration, Complete);
+ }
+ }
+
+ ///
+ /// ProjectManager interface
+ ///
+ /// Task class type
+ /// Resource class type
+ public interface IProjectManager
+ where T : IP_Task
+ where R : class
+ {
+ ///
+ /// Add task to project manager
+ ///
+ ///
+ void Add(T task);
+ ///
+ /// Delete task from project manager
+ ///
+ ///
+ void Delete(T task);
+ ///
+ /// Group the member task under the group task. Group task cannot have relations.
+ ///
+ ///
+ ///
+ void Group(T group, T member);
+ ///
+ /// Ungroup member task from group task. If there are no more task under group, group will become a normal task.
+ ///
+ ///
+ ///
+ void Ungroup(T group, T member);
+ ///
+ /// Split the specified task into consecutive parts part1 and part2.
+ ///
+ /// The regular task to split which has duration of at least 2 to make two parts of 1 time unit duration each.
+ /// New Task part (1) of the split task, with the start time of the original task and the specified duration value.
+ /// New Task part (2) of the split task, starting 1 time unit after part (1) ends and having the remaining of the duration of the origina task.
+ /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.
+ void Split(T task, T part1, T part2, TimeSpan duration);
+ ///
+ /// Split the specified part and obtain another part from it.
+ ///
+ /// The task part to split which has duration of at least 2 to make two parts of 1 time unit duration each. Its duration will be set to the specified duration value.
+ /// New Task part of the original part, starting 1 time unit after it ends and having the remaining of the duration of the original part.
+ /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.
+ void Split(T part, T another, TimeSpan duration);
+ ///
+ /// Join part1 and part2 in a split task into a single part represented by part1, and part2 will be deleted from the ProjectManager.
+ /// The resulting part will have a duration total of the two parts. Schedule of other parts will be packed according to direction of join.
+ /// If the join will result in only one part remaining, the split task will merge instead.
+ ///
+ /// The part to keep in the ProjectManager after the join completes successfully.
+ /// The part to join into part1 and be deleted afterwards from the ProjectManager.
+ void Join(T part1, T part2);
+ ///
+ /// Merge all the parts of the splitted task back into one task, having duration equal to sum of total duration of individual task parts, and aggregating the resources onto the resulting task.
+ ///
+ /// The split Task to merge
+ void Merge(T split);
+ ///
+ /// Get the parts of the split task
+ ///
+ ///
+ ///
+ IEnumerable PartsOf(T split);
+ ///
+ /// Get the split task that the specified part belogs to.
+ ///
+ ///
+ ///
+ T SplitTaskOf(T part);
+ ///
+ /// Get whether the specified task is a split task
+ ///
+ ///
+ ///
+ bool IsSplit(T task);
+ ///
+ /// Get whether the specified task is a part of a split task
+ ///
+ ///
+ ///
+ bool IsPart(T task);
+ ///
+ /// Ungroup all member task under the specfied group task. The specified group task will become a normal task.
+ ///
+ ///
+ void Ungroup(T group);
+ ///
+ /// Move the specified task by offset positions in the task enumeration
+ ///
+ ///
+ ///
+ void Move(T task, int offset);
+ ///
+ /// Set a relation between the precedent and dependant task
+ ///
+ ///
+ ///
+ void Relate(T precedent, T dependant);
+ ///
+ /// Unset the relation between the precedent and dependant task, if any.
+ ///
+ ///
+ ///
+ void Unrelate(T precedent, T dependant);
+ ///
+ /// Remove all dependant task from specified precedent task
+ ///
+ ///
+ void Unrelate(T precedent);
+ ///
+ /// Enumerate through all tasks that is a precedent, having dependants.
+ ///
+ IEnumerable Precedents { get; }
+ ///
+ /// Enumerate through all the tasks in the ProjectManager.
+ /// If there are no change to groups and no add/delete tasks, the order between consecutive calls is preserved.
+ ///
+ IEnumerable Tasks { get; }
+ ///
+ /// Set the start time of the specified task.
+ ///
+ ///
+ /// Number of timescale units after ProjectManager.Start
+ void SetStart(T task, TimeSpan start);
+ ///
+ /// Set the end time of the specified task. Duration is automatically adjusted to satisfy.
+ ///
+ ///
+ /// Number of timescale units after ProjectManager.Start
+ void SetEnd(T task, TimeSpan end);
+ ///
+ /// Set the duration of the specified task from start to end.
+ ///
+ ///
+ /// Number of timescale units between ProjectManager.Start
+ void SetDuration(T task, TimeSpan duration);
+ ///
+ /// Set the percentage complete of the specified task from 0.0f to 1.0f.
+ /// No effect on group tasks as they will get the aggregated percentage complete of all child tasks
+ ///
+ ///
+ ///
+ void SetComplete(T task, float complete);
+ ///
+ /// Set whether to collapse the specified group task. No effect on regular tasks.
+ ///
+ ///
+ ///
+ void SetCollapse(T group, bool collasped);
+ ///
+ /// Set the "now" time. Its value is the number of timescale units after the start time.
+ ///
+ TimeSpan Now { get; }
+ ///
+ /// Set the start date of the project.
+ ///
+ DateTime Start { get; set; }
+ ///
+ /// Get the zero-based index of the specified task
+ ///
+ ///
+ ///
+ int IndexOf(T task);
+ ///
+ /// Enumerate through parent group and grandparent groups of the specified task
+ ///
+ ///
+ ///
+ IEnumerable GroupsOf(T task);
+ ///
+ /// Enumerate through all the children and grandchildren of the specified group
+ ///
+ ///
+ ///
+ IEnumerable MembersOf(T group);
+ ///
+ /// Enumerate through all the direct children of the specified group
+ ///
+ ///
+ ///
+ IEnumerable DirectMembersOf(T group);
+ ///
+ /// Enumerate through all the direct precedents and indirect precedents of the specified task
+ ///
+ ///
+ ///
+ IEnumerable PrecedentsOf(T task);
+ ///
+ /// Enumerate through all the direct dependants and indirect dependants of the specified task
+ ///
+ ///
+ ///
+ IEnumerable DependantsOf(T task);
+ ///
+ /// Enumerate through all the direct precedents of the specified task
+ ///
+ ///
+ ///
+ IEnumerable DirectPrecedentsOf(T task);
+ ///
+ /// Enumerate through all the direct dependants of the specified task
+ ///
+ ///
+ ///
+ IEnumerable DirectDependantsOf(T task);
+ ///
+ /// Enumerate through all the critical paths. Each path is an enumerable of tasks, starting from the final task of each path.
+ ///
+ IEnumerable> CriticalPaths { get; }
+ ///
+ /// Get the parent group of the specified task
+ ///
+ ///
+ ///
+ T DirectGroupOf(T task);
+ ///
+ /// Get whether the specified task is a group
+ ///
+ ///
+ ///
+ bool IsGroup(T task);
+ ///
+ /// Get whether the specified task is a member
+ ///
+ ///
+ ///
+ bool IsMember(T task);
+ ///
+ /// Get whether the specified task has relations, either has dependants or has precedents connecting to it.
+ ///
+ ///
+ ///
+ bool HasRelations(T task);
+ ///
+ /// Assign the specified resource to the specified task
+ ///
+ ///
+ ///
+ void Assign(T task, R resource);
+ ///
+ /// Unassign the specified resource from the specfied task
+ ///
+ ///
+ ///
+ void Unassign(T task, R resource);
+ ///
+ /// Unassign all resources from the specified task
+ ///
+ ///
+ void Unassign(T task);
+ ///
+ /// Unassign the specified resource from all tasks that has this resource assigned
+ ///
+ ///
+ void Unassign(R resource);
+ ///
+ /// Enumerate through all the resources that has been assigned to some task.
+ ///
+ IEnumerable Resources { get; }
+ ///
+ /// Enumerate through all the resources that has been assigned to the specified task.
+ ///
+ ///
+ ///
+ IEnumerable ResourcesOf(T task);
+ ///
+ /// Enumerate through all the tasks that has the specified resource assigned to it.
+ ///
+ ///
+ ///
+ IEnumerable TasksOf(R resource);
+ }
+}
diff --git a/UserClass/Project.cs b/UserClass/Project.cs
new file mode 100644
index 0000000..cd5b9f5
--- /dev/null
+++ b/UserClass/Project.cs
@@ -0,0 +1,1385 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SunlightCentralizedControlManagement_SCCM_.UserClass
+{
+ ///
+ /// Wrapper ProjectManager class
+ ///
+ [Serializable]
+ public class ProjectManager : ProjectManager
+ {
+ }
+
+ ///
+ /// Concrete ProjectManager class for the IProjectManager interface
+ ///
+ ///
+ ///
+ [Serializable]
+ public class ProjectManager : IProjectManager
+ where T : IP_Task
+ where R : class
+ {
+ HashSet _mRegister = new HashSet();
+ List _mRootIP_Tasks = new List();
+ Dictionary> _mMembersOfGroup = new Dictionary>(); // Map group to list of members
+ Dictionary> _mDependantsOfPrecedent = new Dictionary>(); // Map precendent to list of dependents
+ Dictionary> _mResourcesOfIP_Task = new Dictionary>(); // Map IP_Task to list of resources
+ Dictionary> _mPartsOfSplitIP_Task = new Dictionary>(); // Map split IP_Task to list of IP_Task parts
+ Dictionary _mSplitIP_TaskOfPart = new Dictionary(); // Map a IP_Task part to the original split IP_Task
+ Dictionary _mGroupOfMember = new Dictionary(); // Map member IP_Task to parent group IP_Task
+ Dictionary _mIP_TaskIndices = new Dictionary(); // Map the IP_Task to its zero-based index order position
+
+ ///
+ /// Create a new Project
+ ///
+ public ProjectManager()
+ {
+ Now = TimeSpan.Zero;
+ Start = DateTime.Now;
+ }
+
+ ///
+ /// Get or set the TimeSpan we are at now from Start DateTime
+ ///
+ public TimeSpan Now { get; set; }
+
+ ///
+ /// Get or set the starting date for this project
+ ///
+ public DateTime Start { get; set; }
+
+ ///
+ /// Get the date after the specified TimeSpan
+ ///
+ ///
+ ///
+ public DateTime GetDateTime(TimeSpan span)
+ {
+ return this.Start.Add(span);
+ }
+
+ ///
+ /// Create a new T for this Project and add it to the T tree
+ ///
+ ///
+ public void Add(T IP_Task)
+ {
+ if (!this._mRegister.Contains(IP_Task))
+ {
+ _mRegister.Add(IP_Task);
+ _mRootIP_Tasks.Add(IP_Task);
+ _mMembersOfGroup[IP_Task] = new List();
+ _mDependantsOfPrecedent[IP_Task] = new HashSet();
+ _mResourcesOfIP_Task[IP_Task] = new HashSet();
+ _mGroupOfMember[IP_Task] = null;
+ }
+ }
+
+ ///
+ /// Remove IP_Task from this Project
+ ///
+ ///
+ public void Delete(T IP_Task)
+ {
+ if (IP_Task != null
+ && !_mSplitIP_TaskOfPart.ContainsKey(IP_Task) // not a IP_Task part
+ )
+ {
+ // Check if is group so can ungroup the IP_Task
+ if (this.IsGroup(IP_Task))
+ this.Ungroup(IP_Task);
+
+ if (this.IsSplit(IP_Task))
+ this.Merge(IP_Task);
+
+ // Really delete all references
+ _mRootIP_Tasks.Remove(IP_Task);
+ _mMembersOfGroup.Remove(IP_Task);
+ _mDependantsOfPrecedent.Remove(IP_Task);
+ _mResourcesOfIP_Task.Remove(IP_Task);
+ _mGroupOfMember.Remove(IP_Task);
+ _mPartsOfSplitIP_Task.Remove(IP_Task);
+ foreach (var g in _mMembersOfGroup) g.Value.Remove(IP_Task); // optimised: no need to check for contains
+ foreach (var g in _mDependantsOfPrecedent) g.Value.Remove(IP_Task);
+ _mRegister.Remove(IP_Task);
+ }
+ else if (IP_Task != null
+ && _mSplitIP_TaskOfPart.ContainsKey(IP_Task) // must be existing part
+ )
+ {
+ var split = _mSplitIP_TaskOfPart[IP_Task];
+ var parts = _mPartsOfSplitIP_Task[split];
+ if (parts.Count > 2)
+ {
+ parts.Remove(IP_Task); // remove the part from the split IP_Task
+ _mRegister.Remove(IP_Task); // unregister the part
+ _mResourcesOfIP_Task.Remove(IP_Task);
+ _mSplitIP_TaskOfPart.Remove(IP_Task); // remove the reverse lookup
+
+ split.Start = parts.First().Start; // recalculate the split IP_Task
+ split.End = parts.Last().End;
+ split.Duration = split.End - split.Start;
+ }
+ else
+ {
+ this.Merge(split);
+ }
+ }
+ }
+
+ ///
+ /// Add the member T to the group T
+ ///
+ ///
+ ///
+ public void Group(T group, T member)
+ {
+ if (group != null
+ && member != null
+ && _mRegister.Contains(group)
+ )
+ {
+ // if the member is a IP_Task part, assign the split IP_Task to the group instead
+ if (_mSplitIP_TaskOfPart.ContainsKey(member)) member = _mSplitIP_TaskOfPart[member];
+
+ if (_mRegister.Contains(member)
+ && !group.Equals(member)
+ && !_mPartsOfSplitIP_Task.ContainsKey(group) // group cannot be split IP_Task
+ && !_mSplitIP_TaskOfPart.ContainsKey(group) // group cannot be parts
+ && !this.MembersOf(member).Contains(group)
+ && !this.HasRelations(group)
+ )
+ {
+ _DetachIP_Task(member);
+
+ // add member to new group
+ _mMembersOfGroup[group].Add(member);
+ _mGroupOfMember[member] = group;
+
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+ // clear indices since positions changed
+ _mIP_TaskIndices.Clear();
+ }
+ }
+ }
+
+ ///
+ /// Remove the member IP_Task from its group
+ ///
+ public void Ungroup(T group, T member)
+ {
+ if (group != null
+ && member != null
+ && _mRegister.Contains(group)
+ )
+ {
+ // change the member to become the split IP_Task is member is a IP_Task part
+ if (_mSplitIP_TaskOfPart.ContainsKey(member)) member = _mSplitIP_TaskOfPart[member];
+ if (_mRegister.Contains(member) && this.IsGroup(group))
+ {
+ var ancestor = this.GroupsOf(group).LastOrDefault();
+ if(ancestor == null) // group is in root
+ _mRootIP_Tasks.Insert(_mRootIP_Tasks.IndexOf(group) + 1, member);
+ else // group is not in root, we get the ancestor that is in root
+ _mRootIP_Tasks.Insert(_mRootIP_Tasks.IndexOf(ancestor) + 1, member);
+ _mMembersOfGroup[group].Remove(member);
+ _mGroupOfMember[member] = null;
+
+ _RecalculateAncestorsSchedule();
+ }
+ }
+ }
+
+ ///
+ /// Ungroup all member IP_Task under the specfied group IP_Task. The specified group IP_Task will become a normal IP_Task.
+ /// If the there is a grandparent group, the members will become members of the grandparent group.
+ ///
+ ///
+ public void Ungroup(T group)
+ {
+ if (group != null
+ //&& _mRegister.Contains(group)
+ && _mMembersOfGroup.TryGetValue(group, out List members))
+ {
+ var newgroup = this.DirectGroupOf(group);
+ if (newgroup == null)
+ {
+ foreach (var member in members)
+ {
+ _mRootIP_Tasks.Add(member);
+ _mGroupOfMember[member] = null;
+ }
+ }
+ else
+ {
+ foreach (var member in members)
+ {
+ _mMembersOfGroup[newgroup].Add(member);
+ _mGroupOfMember[member] = null;
+ }
+ }
+
+ members.Clear();
+
+ _RecalculateAncestorsSchedule();
+ }
+ }
+
+ ///
+ /// Get the zero-based index of the IP_Task in this Project
+ ///
+ ///
+ ///
+ public int IndexOf(T IP_Task)
+ {
+ if (_mRegister.Contains(IP_Task))
+ {
+ if (_mIP_TaskIndices.ContainsKey(IP_Task))
+ return _mIP_TaskIndices[IP_Task];
+
+ int i = 0;
+ foreach (var x in IP_Tasks)
+ {
+ if (x.Equals(IP_Task))
+ {
+ _mIP_TaskIndices[IP_Task] = i;
+ return i;
+ }
+ i++;
+ }
+ }
+ return -1;
+ }
+
+ ///
+ /// Re-order position of the IP_Task by offset amount of places
+ /// If IP_Task is moved between members, the IP_Task is added to the members' group
+ /// If IP_Task is a member and it is moved above it's group or below last sibling member, then it is moved out of its group
+ /// If IP_Task is a part, then its parent split-IP_Task will be move instead
+ ///
+ ///
+ ///
+ public void Move(T IP_Task, int offset)
+ {
+ if (IP_Task != null && _mRegister.Contains(IP_Task) && offset != 0)
+ {
+ if (IsPart(IP_Task)) IP_Task = SplitIP_TaskOf(IP_Task);
+
+ int indexofIP_Task = IndexOf(IP_Task);
+ if (indexofIP_Task > -1)
+ {
+ int newindexofIP_Task = indexofIP_Task + offset;
+ // check for out of index bounds
+ var IP_Taskcount = IP_Tasks.Count();
+ if (newindexofIP_Task < 0) newindexofIP_Task = 0;
+ else if (newindexofIP_Task > IP_Taskcount) newindexofIP_Task = IP_Taskcount;
+ // get the index of the IP_Task that will be displaced
+ var displacedIP_Task = IP_Tasks.ElementAtOrDefault(newindexofIP_Task);
+
+ if(displacedIP_Task == IP_Task)
+ {
+ return;
+ }
+ if (displacedIP_Task == null)
+ {
+ // adding to the end of the IP_Task list
+ _DetachIP_Task(IP_Task);
+ _mRootIP_Tasks.Add(IP_Task);
+ }
+ else if (!displacedIP_Task.Equals(IP_Task))
+ {
+ int indexofdestinationIP_Task;
+ var displacedIP_Taskparent = this.DirectGroupOf(displacedIP_Task);
+ if (displacedIP_Taskparent == null) // displacedIP_Task is in root
+ {
+ indexofdestinationIP_Task = _mRootIP_Tasks.IndexOf(displacedIP_Task);
+ _DetachIP_Task(IP_Task);
+ _mRootIP_Tasks.Insert(indexofdestinationIP_Task, IP_Task);
+ }
+ else if (!displacedIP_Taskparent.Equals(IP_Task)) // displaced IP_Task is not under the moving IP_Task
+ {
+ var memberlist = _mMembersOfGroup[displacedIP_Taskparent];
+ indexofdestinationIP_Task = memberlist.IndexOf(displacedIP_Task);
+ _DetachIP_Task(IP_Task);
+ memberlist.Insert(indexofdestinationIP_Task, IP_Task);
+ _mGroupOfMember[IP_Task] = displacedIP_Taskparent;
+ }
+ }
+
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+
+ // clear indices since positions changed
+ _mIP_TaskIndices.Clear();
+ }
+ }
+ }
+
+ ///
+ /// Get the T tree
+ ///
+ public IEnumerable IP_Tasks
+ {
+ get
+ {
+ var stack = new Stack(1024);
+ var rstack = new Stack(30);
+ foreach (var IP_Task in _mRootIP_Tasks)
+ {
+ stack.Push(IP_Task);
+ while (stack.Count > 0)
+ {
+ var visited = stack.Pop();
+ yield return visited;
+
+ foreach (var member in _mMembersOfGroup[visited])
+ rstack.Push(member);
+
+ while (rstack.Count > 0) stack.Push(rstack.Pop());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Enumerate upwards from member to and through all the parents and grandparents of the specified IP_Task
+ ///
+ public IEnumerable GroupsOf(T member)
+ {
+ T parent = DirectGroupOf(member);
+ while (parent != null)
+ {
+ yield return parent;
+ parent = DirectGroupOf(parent);
+ }
+ }
+
+ ///
+ /// Enumerate through all the children and grandchildren of the specified group
+ ///
+ ///
+ ///
+ public IEnumerable MembersOf(T group)
+ {
+ if (_mRegister.Contains(group))
+ {
+ Stack stack = new Stack(20);
+ Stack rstack = new Stack(10);
+ foreach (var child in _mMembersOfGroup[group])
+ {
+ stack.Push(child);
+ while (stack.Count > 0)
+ {
+ var visitedchild = stack.Pop();
+ yield return visitedchild;
+
+ // push the grandchild
+ rstack.Clear();
+ foreach (var grandchild in _mMembersOfGroup[visitedchild])
+ rstack.Push(grandchild);
+
+ // put in the right visiting order
+ while (rstack.Count > 0)
+ stack.Push(rstack.Pop());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get the parent group of the specified IP_Task
+ ///
+ ///
+ ///
+ public T DirectGroupOf(T member)
+ {
+ if (_mGroupOfMember.ContainsKey(member)) // _mRegister.Contains(IP_Task))
+ {
+ return _mGroupOfMember[member];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Enumerate through all the direct children of the specified group
+ ///
+ ///
+ ///
+ public IEnumerable DirectMembersOf(T group)
+ {
+ if (group == null) yield break;
+
+ if (_mMembersOfGroup.TryGetValue(group, out List list))
+ {
+ var iter = list.GetEnumerator();
+ while (iter.MoveNext()) yield return iter.Current;
+ }
+ }
+
+ ///
+ /// Enumerate through all the direct precedents and indirect precedents of the specified IP_Task
+ ///
+ ///
+ ///
+ public IEnumerable PrecedentsOf(T dependant)
+ {
+ if (_mRegister.Contains(dependant))
+ {
+ var stack = new Stack(20);
+ foreach (var p in DirectPrecedentsOf(dependant))
+ {
+ stack.Push(p);
+ while (stack.Count > 0)
+ {
+ var visited = stack.Pop();
+ yield return visited;
+ foreach (var grandp in DirectPrecedentsOf(visited))
+ stack.Push(grandp);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Enumerate through all the direct dependants and indirect dependants of the specified IP_Task
+ ///
+ ///
+ ///
+ public IEnumerable DependantsOf(T precendent)
+ {
+ if (!_mDependantsOfPrecedent.ContainsKey(precendent)) yield break;
+
+ var stack = new Stack(20);
+ foreach (var d in _mDependantsOfPrecedent[precendent])
+ {
+ stack.Push(d);
+ while (stack.Count > 0)
+ {
+ var visited = stack.Pop();
+ yield return visited;
+ foreach (var grandd in _mDependantsOfPrecedent[visited])
+ stack.Push(grandd);
+ }
+ }
+ }
+
+ ///
+ /// Enumerate through all the direct precedents of the specified IP_Task
+ ///
+ ///
+ ///
+ public IEnumerable DirectPrecedentsOf(T dependants)
+ {
+ return _mDependantsOfPrecedent.Where(x => x.Value.Contains(dependants)).Select(x => x.Key);
+ }
+
+ ///
+ /// Enumerate through all the direct dependants of the specified IP_Task
+ ///
+ ///
+ ///
+ public IEnumerable DirectDependantsOf(T precedent)
+ {
+ if (precedent == null) yield break;
+
+ if (_mDependantsOfPrecedent.TryGetValue(precedent, out HashSet dependants))
+ {
+ var iter = dependants.GetEnumerator();
+ while (iter.MoveNext()) yield return iter.Current;
+ }
+ }
+
+ ///
+ /// Enumerate through all IP_Tasks that is a precedent, having dependants.
+ ///
+ public IEnumerable Precedents
+ {
+ get { return _mDependantsOfPrecedent.Where(x => _mDependantsOfPrecedent[x.Key].Count > 0).Select(x => x.Key); }
+ }
+
+ ///
+ /// Enumerate list of critical paths in Project
+ ///
+ public IEnumerable> CriticalPaths
+ {
+ get
+ {
+ Dictionary> endtimelookp = new Dictionary>(1024);
+ TimeSpan max_end = TimeSpan.MinValue;
+ foreach (var IP_Task in this.IP_Tasks)
+ {
+ if (!endtimelookp.TryGetValue(IP_Task.End, out List list))
+ endtimelookp[IP_Task.End] = new List(10);
+ endtimelookp[IP_Task.End].Add(IP_Task);
+
+ if (IP_Task.End > max_end) max_end = IP_Task.End;
+ }
+
+ if (max_end != TimeSpan.MinValue)
+ {
+ foreach (var IP_Task in endtimelookp[max_end])
+ {
+ yield return new T[] { IP_Task }.Concat(PrecedentsOf(IP_Task));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get whether the specified IP_Task is a group
+ ///
+ ///
+ ///
+ public bool IsGroup(T IP_Task)
+ {
+ if (_mMembersOfGroup.TryGetValue(IP_Task, out List list))
+ return list.Count > 0;
+ else
+ return false;
+ }
+
+ ///
+ /// Get whether the specified IP_Task is a member
+ ///
+ ///
+ ///
+ public bool IsMember(T IP_Task)
+ {
+ return this.DirectGroupOf(IP_Task) != null;
+ }
+
+ ///
+ /// Get whether the specified IP_Task has relations, either has dependants or has precedents connecting to it.
+ ///
+ ///
+ ///
+ public bool HasRelations(T IP_Task)
+ {
+ if (_mRegister.Contains(IP_Task) && _mDependantsOfPrecedent.ContainsKey(IP_Task))
+ {
+ return _mDependantsOfPrecedent[IP_Task].Count > 0 || DirectPrecedentsOf(IP_Task).FirstOrDefault() != null;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Set a relation between the precedent and dependant IP_Task
+ ///
+ ///
+ ///
+ public void Relate(T precedent, T dependant)
+ {
+ if (_mRegister.Contains(precedent)
+ && _mRegister.Contains(dependant)
+ )
+ {
+ if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent];
+ if (_mSplitIP_TaskOfPart.ContainsKey(dependant)) dependant = _mSplitIP_TaskOfPart[dependant];
+
+ if (!precedent.Equals(dependant)
+ &&!this.DependantsOf(dependant).Contains(precedent)
+ && !this.IsGroup(precedent)
+ && !this.IsGroup(dependant)
+ )
+ {
+ _mDependantsOfPrecedent[precedent].Add(dependant);
+
+ _RecalculateDependantsOf(precedent);
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+ }
+ }
+ }
+
+ ///
+ /// Unset the relation between the precedent and dependant IP_Task, if any.
+ ///
+ ///
+ ///
+ public void Unrelate(T precedent, T dependant)
+ {
+ if (_mRegister.Contains(precedent) && _mRegister.Contains(dependant))
+ {
+ if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent];
+ if (_mSplitIP_TaskOfPart.ContainsKey(dependant)) dependant = _mSplitIP_TaskOfPart[dependant];
+
+ _mDependantsOfPrecedent[precedent].Remove(dependant);
+
+ _RecalculateSlack();
+ }
+ }
+
+ ///
+ /// Remove all dependant IP_Task from specified precedent IP_Task
+ ///
+ ///
+ public void Unrelate(T precedent)
+ {
+ if (_mRegister.Contains(precedent))
+ {
+ if (_mSplitIP_TaskOfPart.ContainsKey(precedent))
+ precedent = _mSplitIP_TaskOfPart[precedent];
+
+ _mDependantsOfPrecedent[precedent].Clear();
+
+ _RecalculateSlack();
+ }
+ }
+
+ ///
+ /// Assign the specified resource to the specified IP_Task
+ ///
+ ///
+ ///
+ public void Assign(T IP_Task, R resource)
+ {
+ if (_mRegister.Contains(IP_Task) && !_mResourcesOfIP_Task[IP_Task].Contains(resource))
+ _mResourcesOfIP_Task[IP_Task].Add(resource);
+ }
+
+ ///
+ /// Unassign the specified resource from the specfied IP_Task
+ ///
+ ///
+ ///
+ public void Unassign(T IP_Task, R resource)
+ {
+ _mResourcesOfIP_Task[IP_Task].Remove(resource);
+ }
+
+ ///
+ /// Unassign the all resources from the specfied IP_Task
+ ///
+ ///
+ public void Unassign(T IP_Task)
+ {
+ if(_mRegister.Contains(IP_Task))
+ _mResourcesOfIP_Task[IP_Task].Clear();
+ }
+
+ ///
+ /// Unassign the specified resource from all IP_Tasks that has this resource assigned
+ ///
+ ///
+ public void Unassign(R resource)
+ {
+ foreach (var r in _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)))
+ r.Value.Remove(resource);
+ }
+
+ ///
+ /// Enumerate through all the resources that has been assigned to some IP_Task.
+ ///
+ public IEnumerable Resources
+ {
+ get
+ {
+ return _mResourcesOfIP_Task.SelectMany(x => x.Value).Distinct();
+ }
+ }
+
+ ///
+ /// Enumerate through all the resources that has been assigned to the specified IP_Task.
+ ///
+ ///
+ ///
+ public IEnumerable ResourcesOf(T IP_Task)
+ {
+ if (IP_Task == null || !_mRegister.Contains(IP_Task))
+ yield break;
+
+ if (_mResourcesOfIP_Task.TryGetValue(IP_Task, out HashSet list))
+ {
+ foreach (var item in list)
+ yield return item;
+ }
+ }
+
+ ///
+ /// Enumerate through all the IP_Tasks that has the specified resource assigned to it.
+ ///
+ ///
+ ///
+ public IEnumerable IP_TasksOf(R resource)
+ {
+ return _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)).Select(x => x.Key);
+ }
+
+ ///
+ /// Set the start value. Affects group start/end and dependants start time.
+ ///
+ public void SetStart(T IP_Task, TimeSpan value)
+ {
+ if (_mRegister.Contains(IP_Task) && value != IP_Task.Start && !this.IsGroup(IP_Task))
+ {
+ _SetStartHelper(IP_Task, value);
+
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+ }
+ // Set start for a group IP_Task
+ else if (_mRegister.Contains(IP_Task) && value != IP_Task.Start && this.IsGroup(IP_Task))
+ {
+ _SetGroupStartHelper(IP_Task, value);
+
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+ }
+ }
+
+ ///
+ /// Set the end time. Affects group end and dependants start time.
+ ///
+ public void SetEnd(T IP_Task, TimeSpan value)
+ {
+ if (_mRegister.Contains(IP_Task) && value != IP_Task.End && !this.IsGroup(IP_Task))
+ {
+ this._SetEndHelper(IP_Task, value);
+
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+ }
+ }
+
+ ///
+ /// Set the duration of the specified IP_Task from start to end.
+ ///
+ ///
+ /// Number of timescale units between ProjectManager.Start
+ public void SetDuration(T IP_Task, TimeSpan duration)
+ {
+ this.SetEnd(IP_Task, IP_Task.Start + duration);
+ }
+
+ ///
+ /// Set the percentage complete of the specified IP_Task from 0.0f to 1.0f.
+ /// No effect on group IP_Tasks as they will get the aggregated percentage complete of all child IP_Tasks
+ ///
+ ///
+ ///
+ public void SetComplete(T IP_Task, float complete)
+ {
+ if (_mRegister.Contains(IP_Task)
+ && complete != IP_Task.Complete
+ && !this.IsGroup(IP_Task) // not a group
+ && !_mPartsOfSplitIP_Task.ContainsKey(IP_Task) // not a split IP_Task
+ )
+ {
+ _SetCompleteHelper(IP_Task, complete);
+
+ _RecalculateComplete();
+ }
+ }
+
+ ///
+ /// Set whether to collapse the specified group IP_Task. No effect on regular IP_Tasks.
+ ///
+ ///
+ ///
+ public void SetCollapse(T IP_Task, bool collasped)
+ {
+ if (_mRegister.Contains(IP_Task) && this.IsGroup(IP_Task))
+ {
+ IP_Task.IsCollapsed = collasped;
+ }
+ }
+
+ ///
+ /// Split the specified IP_Task into consecutive parts part1 and part2.
+ ///
+ /// The regular IP_Task to split which has duration of at least 2 to make two parts of 1 time unit duration each.
+ /// New IP_Task part (1) of the split IP_Task, with the start time of the original IP_Task and the specified duration value.
+ /// New IP_Task part (2) of the split IP_Task, starting 1 time unit after part (1) ends and having the remaining of the duration of the origina IP_Task.
+ /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.
+ public void Split(T IP_Task, T part1, T part2, TimeSpan duration)
+ {
+ if (IP_Task != null
+ && part1 != null
+ && part2 != null
+ && !part1.Equals(part2) // parts cannot be the same
+ && _mRegister.Contains(IP_Task) // IP_Task must be registered
+ && !_mPartsOfSplitIP_Task.ContainsKey(IP_Task) // IP_Task must not already be a split IP_Task
+ && !_mSplitIP_TaskOfPart.ContainsKey(IP_Task) // IP_Task must not be a IP_Task part
+ && _mMembersOfGroup[IP_Task].Count == 0 // IP_Task cannot be a group
+ && !_mRegister.Contains(part1) // part1 and part2 must have never existed
+ && !_mRegister.Contains(part2)
+ )
+ {
+ _mRegister.Add(part1); // register part1
+ _mResourcesOfIP_Task[part1] = new HashSet(); // create container for holding resource
+
+ // add part1 to split IP_Task
+ IP_Task.Complete = 0.0f; // reset the complete status
+ var parts = _mPartsOfSplitIP_Task[IP_Task] = new List(2);
+ parts.Add(part1);
+ _mSplitIP_TaskOfPart[part1] = IP_Task; // make a reverse lookup
+
+ // allign the schedule
+ if (duration <= TimeSpan.Zero || duration >= IP_Task.Duration) duration = TimeSpan.FromTicks(IP_Task.Duration.Ticks / 2);
+ part1.Start = IP_Task.Start;
+ part1.End = IP_Task.End;
+ part1.Duration = IP_Task.Duration;
+
+ // split part1 to give part2
+ this.Split(part1, part2, duration);
+ }
+ }
+
+ ///
+ /// Split the specified part and obtain another part from it.
+ ///
+ /// The IP_Task part to split which has duration of at least 2 to make two parts of 1 time unit duration each. Its duration will be set to the specified duration value.
+ /// New IP_Task part of the original part, starting 1 time unit after it ends and having the remaining of the duration of the original part.
+ /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.
+ public void Split(T part, T other, TimeSpan duration)
+ {
+ if (part != null
+ && other != null
+ && _mSplitIP_TaskOfPart.ContainsKey(part) // part must be an existing part
+ && !_mRegister.Contains(other) // other must not have existed
+ )
+ {
+ _mRegister.Add(other); // register other part
+ _mResourcesOfIP_Task[other] = new HashSet(); // create container for holding resource
+
+ var split = _mSplitIP_TaskOfPart[part]; // get the split IP_Task
+ var parts = _mPartsOfSplitIP_Task[split]; // get the list of ordered parts
+
+ parts.Insert(parts.IndexOf(part) + 1, other); // insert the other part after the existing part
+ _mSplitIP_TaskOfPart[other] = split; // set the reverse lookup
+
+ System.Diagnostics.Debug.Write("Project::Split(T part, T other, TimeSpan duration): Need to define minimum duration for splitting.");
+
+ // limit the duration point within the split IP_Task duration
+ if (duration <= TimeSpan.Zero || duration >= part.Duration) duration = TimeSpan.FromTicks(part.Duration.Ticks / 2);
+
+ // the real split
+ var one_duration = duration;
+ var two_duration = part.Duration - duration;
+ part.Duration = one_duration;
+ part.End = part.Start + one_duration;
+ other.Duration = two_duration;
+ other.Start = part.End;
+ other.End = other.Start + two_duration;
+
+ _PackPartsForward(parts);
+ split.Start = parts.First().Start; // recalculate the split IP_Task
+ split.End = parts.Last().End;
+ split.Duration = split.End - split.Start;
+
+ _RecalculateDependantsOf(split);
+ _RecalculateAncestorsSchedule();
+ }
+ }
+
+ ///
+ /// Join part1 and part2 in a split IP_Task into a single part represented by part1, and part2 will be deleted from the ProjectManager.
+ /// The resulting part will have a duration total of the two parts.
+ /// Part1 and part2 must be actual parts and must be consecutive parts in the split IP_Task.
+ /// If the join results in only one part remaining, the all parts will be deleted and the split IP_Task will promote to a regular IP_Task
+ /// Schedule of other parts will not be affected.
+ /// TODO: Join option: EarlyStartLateEnd, EarlyStartEarlyEnd, LateStartLateEnd
+ ///
+ /// The part to keep in the ProjectManager after the join completes successfully.
+ /// The part to join into part1 and be deleted afterwards from the ProjectManager.
+ public void Join(T part1, T part2)
+ {
+ if (part1 != null
+ && part2 != null
+ && _mSplitIP_TaskOfPart.ContainsKey(part1) // part1 and part2 must already be existing parts
+ && _mSplitIP_TaskOfPart.ContainsKey(part2)
+ && _mSplitIP_TaskOfPart[part1] == _mSplitIP_TaskOfPart[part2] // part1 and part2 must be of the same split IP_Task
+ )
+ {
+
+ var split = _mSplitIP_TaskOfPart[part1];
+ var parts = _mPartsOfSplitIP_Task[split];
+ if (parts.Count > 2)
+ {
+ // Aggregate part2 into part1, and determine join type
+ TimeSpan min; bool join_backwards;
+ if (part1.Start < part2.Start) { min = part1.Start; join_backwards = true; }
+ else { min = part2.Start; join_backwards = false; }
+ TimeSpan duration = part1.Duration + part2.Duration;
+
+ part1.Start = min;
+ part1.Duration = duration;
+ part1.End = min + duration;
+
+ // aggregate resouces
+ // TODO: Ask whether to aggregate resources?
+ foreach (var r in this.ResourcesOf(part2))
+ this.Assign(part1, r);
+ this.Unassign(part2);
+
+ // remove all traces of part2
+ parts.Remove(part2);
+ _mResourcesOfIP_Task.Remove(part2);
+ _mSplitIP_TaskOfPart.Remove(part2);
+ _mRegister.Remove(part2);
+
+ // pack the remaining parts
+ if (join_backwards) _PackPartsForward(parts);
+ else _PackPartsBackwards(parts);
+
+ // set the duration
+ split.End = parts.Last().End;
+ split.Duration = split.End - split.Start;
+ split.Start = parts.First().Start;
+
+ _RecalculateAncestorsSchedule();
+ }
+ else
+ {
+ this.Merge(split);
+ }
+ }
+ }
+
+ ///
+ /// Merge all the parts of the splitted IP_Task back into one IP_Task, having duration equal to sum of total duration of individual IP_Task parts, and aggregating the resources onto the resulting IP_Task.
+ ///
+ /// The split IP_Task to merge
+ public void Merge(T split)
+ {
+ if (split != null
+ && _mPartsOfSplitIP_Task.ContainsKey(split) // must be existing split IP_Task
+ )
+ {
+ TimeSpan duration = TimeSpan.Zero;
+ _mPartsOfSplitIP_Task[split].ForEach(x => {
+
+ // sum durations
+ duration += x.Duration;
+
+ // merge resources onto split IP_Task
+ foreach (var r in _mResourcesOfIP_Task[x])
+ this.Assign(split, r);
+
+ // remove traces of all parts
+ _mSplitIP_TaskOfPart.Remove(x);
+ _mRegister.Remove(x);
+ _mResourcesOfIP_Task.Remove(x);
+ });
+ _mPartsOfSplitIP_Task.Remove(split); // remove split as a split IP_Task
+
+ // set the duration
+ this.SetDuration(split, duration);
+ }
+ }
+
+ ///
+ /// Get the parts of the split IP_Task
+ ///
+ ///
+ ///
+ public IEnumerable PartsOf(T split)
+ {
+ if (split != null
+ && _mPartsOfSplitIP_Task.ContainsKey(split) // must be existing split IP_Task
+ )
+ {
+ return _mPartsOfSplitIP_Task[split].Select(x => x);
+ }
+ else
+ {
+ return new T[0];
+ }
+ }
+
+ ///
+ /// Get the split IP_Task that the specified part belogs to.
+ ///
+ ///
+ ///
+ public T SplitIP_TaskOf(T part)
+ {
+ if (_mSplitIP_TaskOfPart.ContainsKey(part))
+ return _mSplitIP_TaskOfPart[part];
+ return null;
+ }
+
+ ///
+ /// Get whether the specified IP_Task is a split IP_Task
+ ///
+ ///
+ ///
+ public bool IsSplit(T IP_Task)
+ {
+ return IP_Task != null && _mPartsOfSplitIP_Task.ContainsKey(IP_Task);
+ }
+
+ ///
+ /// Get whether the specified IP_Task is a part of a split IP_Task
+ ///
+ ///
+ ///
+ public bool IsPart(T IP_Task)
+ {
+ return IP_Task != null && _mSplitIP_TaskOfPart.ContainsKey(IP_Task);
+ }
+
+ ///
+ /// Detach the specified IP_Task from ProjectManager.IP_Tasks (i.e. remove from its parent group, or if not it goes not have a parent group, unregister from root IP_Task status).
+ /// The specified IP_Task will remain registered in ProjectManager.
+ /// After execution of this helper method, the IP_Task is expected to be re-attached to ProjectManager.IP_Tasks by regaining root IP_Task status, or joining a new group.
+ ///
+ ///
+ private void _DetachIP_Task(T IP_Task)
+ {
+ var group = this.DirectGroupOf(IP_Task);
+ if (group == null) // member is actually not in any group, so it must be in _mRootIP_Tasks
+ _mRootIP_Tasks.Remove(IP_Task);
+ else
+ {
+ _mMembersOfGroup[group].Remove(IP_Task);
+ _mGroupOfMember[IP_Task] = null;
+ }
+ }
+
+ private void _SetStartHelper(T IP_Task, TimeSpan value)
+ {
+ if (IP_Task.Start != value)
+ {
+ if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
+ {
+ // IP_Task part belonging to a split IP_Task needs special treatment
+ _SetPartStartHelper(IP_Task, value);
+ }
+ else // regular IP_Task or a split IP_Task, which we will treat normally
+ {
+ // check out of bounds
+ if (value < TimeSpan.Zero) value = TimeSpan.Zero;
+ if (this.DirectPrecedentsOf(IP_Task).Any())
+ {
+ var max_end = this.DirectPrecedentsOf(IP_Task).Max(x => x.End);
+ if (value <= max_end) value = max_end; // + One;
+ }
+
+ // save offset just in case we need to use for moving IP_Task parts
+ var offset = value - IP_Task.Start;
+
+ // cache value
+ IP_Task.Duration = IP_Task.End - IP_Task.Start;
+ IP_Task.Start = value;
+
+ // affect self
+ IP_Task.End = IP_Task.Start + IP_Task.Duration;
+
+ // calculate dependants
+ _RecalculateDependantsOf(IP_Task);
+
+ // shift the IP_Task parts accordingly if IP_Task was a split IP_Task
+ if (_mPartsOfSplitIP_Task.ContainsKey(IP_Task))
+ {
+ _mPartsOfSplitIP_Task[IP_Task].ForEach(x => {
+ x.Start += offset;
+ x.End += offset;
+ });
+ }
+ }
+ }
+ }
+
+ ///
+ /// Set the start date for a group IP_Task. The relative dates between the IP_Tasks in the group will not be affected
+ ///
+ ///
+ ///
+ private void _SetGroupStartHelper(T group, TimeSpan value)
+ {
+ if (_mRegister.Contains(group) && value != group.Start && this.IsGroup(group))
+ {
+ bool earlier = value < group.Start;
+ TimeSpan offset = value - group.Start;
+ var decendants = earlier ? MembersOf(group).OrderBy((t) => t.Start) : MembersOf(group).OrderByDescending((t) => t.Start);
+
+ foreach(T decendant in decendants)
+ {
+ if (this.IsGroup(decendant)) continue;
+
+ decendant.Start += offset;
+ decendant.End += offset;
+
+ if (this.IsSplit(decendant))
+ {
+ var parts = _mPartsOfSplitIP_Task[decendant];
+ foreach (T part in parts)
+ {
+ part.Start += offset;
+ part.End += offset;
+ }
+ }
+
+ _RecalculateDependantsOf(decendant);
+ }
+
+ _RecalculateAncestorsSchedule();
+ _RecalculateSlack();
+ }
+ }
+
+ private void _SetEndHelper(T IP_Task, TimeSpan value)
+ {
+ if (IP_Task.End != value)
+ {
+ if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
+ {
+ // IP_Task part belonging to a split IP_Task needs special treatment
+ _SetPartEndHelper(IP_Task, value);
+ }
+ else // regular IP_Task or a split IP_Task, which we will treat normally
+ {
+ // check bounds
+ bool isSplitIP_Task = _mPartsOfSplitIP_Task.ContainsKey(IP_Task);
+ T last_part = null;
+ if (isSplitIP_Task)
+ {
+ last_part = _mPartsOfSplitIP_Task[IP_Task].Last();
+ if (value <= last_part.Start) value = last_part.Start + TimeSpan.FromMinutes(30);
+ }
+ if (value <= IP_Task.Start) value = IP_Task.Start + TimeSpan.FromMinutes(30); // end cannot be less than start
+
+ // assign end value
+ IP_Task.End = value;
+ IP_Task.Duration = IP_Task.End - IP_Task.Start;
+
+ _RecalculateDependantsOf(IP_Task);
+
+ if (isSplitIP_Task)
+ {
+ last_part.End = value;
+ last_part.Duration = last_part.End - last_part.Start;
+ }
+ }
+ }
+ }
+
+ private void _SetPartStartHelper(T part, TimeSpan value)
+ {
+ var split = _mSplitIP_TaskOfPart[part];
+ var parts = _mPartsOfSplitIP_Task[split];
+
+ // check bounds
+ if (this.DirectPrecedentsOf(split).Any())
+ {
+ var max_end = this.DirectPrecedentsOf(split).Max(x => x.End);
+ if (value < max_end) value = max_end;
+ }
+ if (value < TimeSpan.Zero) value = TimeSpan.Zero;
+
+ // flag whether we need to pack parts forward or backwards
+ bool backwards = value < part.Start;
+
+ // assign start value, maintining duration and modifying end
+ var duration = part.End - part.Start;
+ part.Start = value;
+ part.End = value + duration;
+
+ // pack packs
+ if (backwards) _PackPartsBackwards(parts);
+ else _PackPartsForward(parts);
+
+ // recalculate the split
+ split.Start = parts.First().Start; // recalculate the split IP_Task
+ split.End = parts.Last().End;
+ split.Duration = split.End - split.Start;
+
+ _RecalculateDependantsOf(split);
+ }
+
+ private void _SetPartEndHelper(T part, TimeSpan value)
+ {
+ var split = _mSplitIP_TaskOfPart[part];
+ var parts = _mPartsOfSplitIP_Task[split];
+
+ // check for bounds
+ if (value <= part.Start) value = part.Start + TimeSpan.FromMinutes(30);
+
+ // flag whether duration is increased or reduced
+ bool increased = value > part.End;
+
+ // set end value and duration
+ part.End = value;
+ part.Duration = part.End - part.Start;
+
+ // pack parts
+ if (increased) _PackPartsForward(parts);
+
+ // recalculate the split
+ split.Start = parts.First().Start; // recalculate the split IP_Task
+ split.End = parts.Last().End;
+ split.Duration = split.End - split.Start;
+
+ _RecalculateDependantsOf(split);
+ }
+
+ private void _PackPartsBackwards(List parts)
+ {
+ // pack backwards first before packing forward again
+ for (int i = parts.Count - 2; i > 0; i--) // Cannot pack beyond first part (i > 0)
+ {
+ var earlier = parts[i];
+ var later = parts[i + 1];
+ if (later.Start <= earlier.End)
+ {
+ earlier.End = later.Start;
+ earlier.Start = earlier.End - earlier.Duration;
+ }
+ }
+
+ _PackPartsForward(parts);
+ }
+
+ private void _PackPartsForward(List parts)
+ {
+ for (int i = 1; i < parts.Count; i++)
+ {
+ var current = parts[i];
+ var previous = parts[i - 1];
+ if (previous.End >= current.Start)
+ {
+ current.Start = previous.End;
+ current.End = current.Start + current.Duration;
+ }
+ }
+ }
+
+ private void _SetCompleteHelper(T IP_Task, float value)
+ {
+ if (IP_Task.Complete != value)
+ {
+ if (value > 1) value = 1;
+ else if (value < 0) value = 0;
+ IP_Task.Complete = value;
+
+ if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
+ {
+ var split = _mSplitIP_TaskOfPart[IP_Task];
+ var parts = _mPartsOfSplitIP_Task[split];
+ float complete = 0;
+ TimeSpan duration = TimeSpan.Zero;
+ foreach (var part in parts)
+ {
+ complete += part.Complete * part.Duration.Ticks;
+ duration += part.Duration;
+ }
+ split.Complete = complete / duration.Ticks;
+ }
+ }
+ }
+
+ private void _RecalculateComplete()
+ {
+ Stack groups = new Stack();
+ foreach (var IP_Task in _mRootIP_Tasks.Where(x => this.IsGroup(x)))
+ {
+ _RecalculateCompletedHelper(IP_Task);
+ }
+ }
+
+ private float _RecalculateCompletedHelper(T groupOrSplit)
+ {
+ float t_complete = 0;
+ TimeSpan t_duration = TimeSpan.Zero;
+
+ if (_mPartsOfSplitIP_Task.ContainsKey(groupOrSplit))
+ {
+ foreach (var part in _mPartsOfSplitIP_Task[groupOrSplit])
+ {
+ t_complete += part.Complete * part.Duration.Ticks;
+ t_duration += part.Duration;
+ }
+ }
+ else
+ {
+ foreach (var member in this.DirectMembersOf(groupOrSplit))
+ {
+ t_duration += member.Duration;
+ if (this.IsGroup(member)) t_complete += _RecalculateCompletedHelper(member) * member.Duration.Ticks;
+ else t_complete += member.Complete * member.Duration.Ticks;
+ }
+ }
+
+ groupOrSplit.Complete = t_complete / t_duration.Ticks;
+
+
+ return groupOrSplit.Complete;
+ }
+
+ private void _RecalculateDependantsOf(T precedent)
+ {
+ // affect decendants
+ foreach (var dependant in this.DirectDependantsOf(precedent))
+ {
+ if (dependant.Start < precedent.End)
+ this._SetStartHelper(dependant, precedent.End);
+ }
+ }
+
+ private void _RecalculateAncestorsSchedule()
+ {
+ // affects parent group
+ foreach(var group in _mRootIP_Tasks.Where(x => this.IsGroup(x)))
+ {
+ _RecalculateAncestorsScheduleHelper(group);
+ }
+ }
+
+ private void _RecalculateAncestorsScheduleHelper(T group)
+ {
+ float t_complete = 0;
+ TimeSpan t_duration = TimeSpan.Zero;
+ var start = TimeSpan.MaxValue;
+ var end = TimeSpan.MinValue;
+ foreach (var member in this.DirectMembersOf(group))
+ {
+ if (this.IsGroup(member))
+ _RecalculateAncestorsScheduleHelper(member);
+
+ t_duration += member.Duration;
+ t_complete += member.Complete * member.Duration.Ticks;
+ if (member.Start < start) start = member.Start;
+ if (member.End > end) end = member.End;
+ }
+
+ this._SetStartHelper(group, start);
+ this._SetEndHelper(group, end);
+ this._SetCompleteHelper(group, t_complete / t_duration.Ticks);
+ }
+
+ private void _RecalculateSlack()
+ {
+ var max_end = this.IP_Tasks.Max(x => x.End);
+ foreach (var IP_Task in this.IP_Tasks)
+ {
+ // affects slack for current IP_Task
+ if (this.DirectDependantsOf(IP_Task).Any())
+ {
+ // slack until the earliest dependant needs to start
+ var min = this.DirectDependantsOf(IP_Task).Min(x => x.Start);
+ IP_Task.Slack = min - IP_Task.End;
+ }
+ else
+ {
+ // no dependants, so we have all the time until the last IP_Task ends
+ IP_Task.Slack = max_end - IP_Task.End;
+ }
+ }
+ }
+
+
+ }
+}
diff --git a/View/ProductionPlanningView.xaml b/View/ProductionPlanningView.xaml
index 6b11512..80b4b1c 100644
--- a/View/ProductionPlanningView.xaml
+++ b/View/ProductionPlanningView.xaml
@@ -21,6 +21,7 @@
+
diff --git a/View/ProductionPlanningView.xaml.cs b/View/ProductionPlanningView.xaml.cs
index 85243df..ff42316 100644
--- a/View/ProductionPlanningView.xaml.cs
+++ b/View/ProductionPlanningView.xaml.cs
@@ -24,6 +24,7 @@ using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
+using System.Drawing.Drawing2D;
using System.Xml.Linq;
using static SunlightCentralizedControlManagement_SCCM_.UserClass.SqliteHelper;
using static SunlightCentralizedControlManagement_SCCM_.WindowsView.ViewStep;
@@ -50,10 +51,11 @@ namespace SunlightCentralizedControlManagement_SCCM_.View
-
+
+
}
private void ListViewItem_Quit(object sender, System.Windows.Input.MouseButtonEventArgs e)//退出事件