#region License Information /* HeuristicLab * Copyright (C) 2002-2017 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using HeuristicLab.Collections; using HeuristicLab.Common.Resources; using HeuristicLab.Core; using HeuristicLab.Core.Views; using HeuristicLab.MainForm; using HeuristicLab.MainForm.WindowsForms; namespace HeuristicLab.Clients.Hive.JobManager.Views { [View("Hive Project Selector View")] [Content(typeof(IItemList), true)] public partial class HiveProjectSelector : ItemView, IDisposable { private const int greenFlagImageIndex = 0; private const int redFlagImageIndex = 1; private const int slaveImageIndex = 0; private const int slaveGroupImageIndex = 1; public const string additionalSlavesGroupName = "Additional Slaves"; public const string additionalSlavesGroupDescription = "Contains additional slaves which are either ungrouped or the parenting slave group is not assigned to the selected project."; private const string CURRENT_SELECTION_TAG = " [current selection]"; private const string NEW_SELECTION_TAG = " [new selection]"; private const string CHANGED_SELECTION_TAG = " [changed selection]"; private const string ADDED_SELECTION_TAG = " [added selection]"; private const string REMOVED_SELECTION_TAG = " [removed selection]"; private const string INCLUDED_TAG = " [included]"; private const string ADDED_INCLUDE_TAG = " [added include]"; private const string REMOVED_INCLUDE_TAG = " [removed include]"; private readonly HashSet mainTreeNodes = new HashSet(); private readonly HashSet filteredTreeNodes = new HashSet(); private readonly HashSet nodeStore = new HashSet(); private readonly HashSet availableResources = new HashSet(); private readonly HashSet assignedResources = new HashSet(); private readonly HashSet includedResources = new HashSet(); private readonly HashSet newAssignedResources = new HashSet(); private readonly HashSet newIncludedResources = new HashSet(); private readonly Color addedAssignmentColor = Color.FromArgb(255, 87, 191, 193); // #57bfc1 private readonly Color removedAssignmentColor = Color.FromArgb(255, 236, 159, 72); // #ec9f48 private readonly Color addedIncludeColor = Color.FromArgb(25, 169, 221, 221); // #a9dddd private readonly Color removedIncludeColor = Color.FromArgb(25, 249, 210, 145); // #f9d291 private readonly Color selectedBackColor = Color.DodgerBlue; private readonly Color selectedForeColor = Color.White; private readonly Color controlTextColor = SystemColors.ControlText; private readonly Color grayTextColor = SystemColors.GrayText; private string currentSearchString; private void resetHiveResourceSelector() { lastSelectedProject = null; selectedProject = null; projectId = null; } private Guid jobId; public Guid JobId { get { return jobId; } set { if (jobId == value) return; jobId = value; resetHiveResourceSelector(); } } private Guid? projectId; public Guid? ProjectId { get { return projectId; } set { if (projectId == value) return; projectId = value; } } private Guid? selectedProjectId; public Guid? SelectedProjectId { get { return selectedProjectId; } set { if (selectedProjectId == value) return; selectedProjectId = value; } } private IEnumerable selectedResourceIds; public IEnumerable SelectedResourceIds { get { return selectedResourceIds; } set { if (selectedResourceIds == value) return; selectedResourceIds = value; } } public bool ChangedProjectSelection { get { if ((lastSelectedProject == null && selectedProject != null) || (lastSelectedProject != null && selectedProject == null) || (lastSelectedProject != null && selectedProject != null && lastSelectedProject.Id != selectedProject.Id)) return true; else return false; } } public bool ChangedResources { get { return !assignedResources.SetEquals(newAssignedResources); } } private Project lastSelectedProject; private Project selectedProject; public Project SelectedProject { get { return selectedProject; } set { if (selectedProject == value) return; if ((JobId == Guid.Empty || JobId == null) && (value == null || SelectedProject == null || value.Id != SelectedProject.Id)) selectedResourceIds = null; lastSelectedProject = selectedProject; selectedProject = value; UpdateResourceTree(); ExtractStatistics(); OnSelectedProjectChanged(); } } public IEnumerable AssignedResources { get { return newAssignedResources; } set { if (newAssignedResources == value) return; newAssignedResources.Clear(); foreach(var resource in value) { newAssignedResources.Add(resource); } } } public new IItemList Content { get { return (IItemList)base.Content; } set { base.Content = value; } } public HiveProjectSelector() { InitializeComponent(); projectsImageList.Images.Add(VSImageLibrary.FlagGreen); projectsImageList.Images.Add(VSImageLibrary.FlagRed); resourcesImageList.Images.Add(VSImageLibrary.MonitorLarge); resourcesImageList.Images.Add(VSImageLibrary.NetworkCenterLarge); } #region Overrides protected override void OnContentChanged() { base.OnContentChanged(); if (Content != null) { if (SelectedProjectId.HasValue && SelectedProjectId.Value != Guid.Empty) { SelectedProject = GetSelectedProjectById(SelectedProjectId.Value); } else { SelectedProject = null; } //ExtractStatistics(); UpdateProjectTree(); } else { lastSelectedProject = null; selectedProject = null; selectedProjectId = null; projectsTreeView.Nodes.Clear(); resourcesTreeView.Nodes.Clear(); } } #endregion #region Event Handlers private void HiveProjectSelector_Load(object sender, EventArgs e) { projectsTreeView.Nodes.Clear(); resourcesTreeView.Nodes.Clear(); } private void searchTextBox_TextChanged(object sender, EventArgs e) { currentSearchString = searchTextBox.Text.ToLower(); //UpdateFilteredTree(); UpdateProjectTree(); } private void projectsTreeView_MouseDoubleClick(object sender, MouseEventArgs e) { OnProjectsTreeViewDoubleClicked(); } private void projectsTreeView_AfterSelect(object sender, TreeViewEventArgs e) { var node = (Project)e.Node.Tag; if (node == null) { projectsTreeView.SelectedNode = null; } else { ResetTreeNodes(projectsTreeView.Nodes); e.Node.BackColor = selectedBackColor; e.Node.ForeColor = selectedForeColor; if(node.Id == projectId) { e.Node.Text += CURRENT_SELECTION_TAG; } else if(projectId == null || projectId == Guid.Empty) { e.Node.Text += NEW_SELECTION_TAG; } else { e.Node.Text += CHANGED_SELECTION_TAG; } } SelectedProject = node; } private void resourcesTreeView_MouseDown(object sender, MouseEventArgs e) { var node = resourcesTreeView.GetNodeAt(new Point(e.X, e.Y)); if (node == null) { resourcesTreeView.SelectedNode = null; ExtractStatistics(); } else { ExtractStatistics((Resource)node.Tag); } } private void resourcesTreeView_BeforeCheck(object sender, TreeViewCancelEventArgs e) { var checkedResource = (Resource)e.Node.Tag; if (newIncludedResources.Contains(checkedResource) || checkedResource.Id == Guid.Empty) { e.Cancel = true; ExtractStatistics((Resource)resourcesTreeView.SelectedNode?.Tag); } } private void resourcesTreeView_AfterCheck(object sender, TreeViewEventArgs e) { var checkedResource = (Resource)e.Node.Tag; if (e.Node.Checked) { newAssignedResources.Add(checkedResource); } else { newAssignedResources.Remove(checkedResource); } UpdateResourceTreeAfterCheck(); ExtractStatistics(); OnAssignedResourcesChanged(); } private void resourcesTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) { return; } #endregion #region Helpers private Project GetSelectedProjectById(Guid projectId) { return Content.Where(x => x.Id == projectId).SingleOrDefault(); } private void UpdateProjectTree() { if (string.IsNullOrEmpty(currentSearchString)) { BuildProjectTree(Content); } else { HashSet filteredProjects = new HashSet(); foreach(var project in Content) { if(project.Name.ToLower().Contains(currentSearchString.ToLower())) { filteredProjects.Add(project); filteredProjects.UnionWith(Content.Where(p => HiveClient.Instance.ProjectAncestors[project.Id].Contains(p.Id))); } } BuildProjectTree(filteredProjects); } } private void BuildProjectTree(IEnumerable projects) { projectsTreeView.Nodes.Clear(); if (!projects.Any()) return; // select all top level projects (withouth parent, or without any ancestor within current project collection) var mainProjects = new HashSet(projects.Where(x => x.ParentProjectId == null)); var parentedMainProjects = new HashSet(projects .Where(x => x.ParentProjectId.HasValue && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value) && !projects.SelectMany(y => HiveClient.Instance.ProjectAncestors[y.Id]).Contains(x.ParentProjectId.Value))); mainProjects.UnionWith(parentedMainProjects); var subProbjects = new HashSet(projects.Except(mainProjects)); foreach(var p in subProbjects) { p.ParentProjectId = HiveClient.Instance.ProjectAncestors[p.Id].Where(x => projects.Select(y => y.Id).Contains(x)).FirstOrDefault(); } var stack = new Stack(mainProjects.OrderByDescending(x => x.Name)); TreeNode currentNode = null; Project currentProject = null; while(stack.Any()) { var newProject = stack.Pop(); var newNode = new TreeNode(newProject.Name) { Tag = newProject }; while (currentNode != null && newProject.ParentProjectId != currentProject.Id) { currentNode = currentNode.Parent; currentProject = currentNode == null ? null : (Project)currentNode.Tag; } if (currentNode == null) { projectsTreeView.Nodes.Add(newNode); newNode.ImageIndex = greenFlagImageIndex; } else { currentNode.Nodes.Add(newNode); newNode.ImageIndex = redFlagImageIndex; } newNode.SelectedImageIndex = newNode.ImageIndex; if (SelectedProject != null && SelectedProject.Id.Equals(newProject.Id)) { newNode.BackColor = selectedBackColor; newNode.ForeColor = selectedForeColor; if(SelectedProject.Id == projectId) { newNode.Text += CURRENT_SELECTION_TAG; } else if (projectId == null || projectId == Guid.Empty) { newNode.Text += NEW_SELECTION_TAG; } else { newNode.Text += CHANGED_SELECTION_TAG; } } if (!string.IsNullOrEmpty(currentSearchString) && newProject.Name.ToLower().Contains(currentSearchString.ToLower())) { newNode.BackColor = Color.LightBlue; } var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id); if (childProjects.Any()) { foreach (var project in childProjects.OrderByDescending(x => x.Name)) { subProbjects.Remove(project); stack.Push(project); } currentNode = newNode; currentProject = newProject; } } projectsTreeView.ExpandAll(); } private static IEnumerable GetAssignedResourcesForProject(Guid projectId) { var assignedProjectResources = HiveServiceLocator.Instance.CallHiveService(s => s.GetAssignedResourcesForProject(projectId)); return HiveClient.Instance.Resources.Where(x => assignedProjectResources.Select(y => y.ResourceId).Contains(x.Id)); } private static IEnumerable GetAssignedResourcesForJob(Guid jobId) { var assignedJobResources = HiveServiceLocator.Instance.CallHiveService(s => s.GetAssignedResourcesForJob(jobId)); return HiveClient.Instance.Resources.Where(x => assignedJobResources.Select(y => y.ResourceId).Contains(x.Id)); } private void UpdateResourceTree() { UpdateAvailableResources(); UpdateAssignedResources(); UpdateIncludedResources(); BuildResourceTree(availableResources); } private void UpdateResourceTreeAfterCheck() { resourcesTreeView.BeforeCheck -= resourcesTreeView_BeforeCheck; resourcesTreeView.AfterCheck -= resourcesTreeView_AfterCheck; UpdateNewAssignedResources(); UpdateNewIncludedResources(); SetTreeNodes(resourcesTreeView.Nodes); resourcesTreeView.BeforeCheck += resourcesTreeView_BeforeCheck; resourcesTreeView.AfterCheck += resourcesTreeView_AfterCheck; } private void UpdateAvailableResources() { availableResources.Clear(); if (selectedProject != null) { var assignedProjectResources = GetAssignedResourcesForProject(selectedProject.Id); foreach (var resource in assignedProjectResources) { availableResources.Add(resource); foreach(var descendant in HiveClient.Instance.Resources.Where(x => HiveClient.Instance.ResourceDescendants[resource.Id].Contains(x.Id))) { availableResources.Add(descendant); } } } //ExtractStatistics(); //OnAssignedResourcesChanged(); } private void UpdateAssignedResources() { assignedResources.Clear(); newAssignedResources.Clear(); if (JobId == Guid.Empty || JobId == null) { // new, unchanged jobs get all avaialable resources // update new assigned resources if(selectedResourceIds == null) { foreach (var resource in availableResources .Where(x => !x.ParentResourceId.HasValue || !availableResources.Select(y => y.Id).Contains(x.ParentResourceId.Value))) { newAssignedResources.Add(resource); } } else { foreach(var resource in availableResources.Where(x => selectedResourceIds.Contains(x.Id))) { newAssignedResources.Add(resource); } } } else { // existent, unchanged jobs get all assigned resources // update assigned resources var assignedJobResources = GetAssignedResourcesForJob(JobId); foreach (var resource in assignedJobResources) { assignedResources.Add(resource); if (selectedResourceIds == null) { newAssignedResources.Add(resource); } } if(selectedResourceIds != null) { foreach (var resource in availableResources.Where(x => selectedResourceIds.Contains(x.Id))) { newAssignedResources.Add(resource); } } } //ExtractStatistics(); OnAssignedResourcesChanged(); } private void UpdateNewAssignedResources() { for(int i = newAssignedResources.Count-1; i>=0; i--) { if(newAssignedResources.Intersect(HiveClient.Instance.GetAvailableParentResources(newAssignedResources.ElementAt(i).Id)).Any()) { newAssignedResources.Remove(newAssignedResources.ElementAt(i)); } } } private void UpdateIncludedResources() { includedResources.Clear(); newIncludedResources.Clear(); if (JobId != Guid.Empty) { foreach (var item in assignedResources) { foreach (var descendant in HiveClient.Instance.GetAvailableChildResources(item.Id)) { includedResources.Add(descendant); } } } foreach (var item in newAssignedResources) { foreach (var descendant in HiveClient.Instance.GetAvailableChildResources(item.Id)) { newIncludedResources.Add(descendant); } } } private void UpdateNewIncludedResources() { newIncludedResources.Clear(); foreach (var item in newAssignedResources) { foreach (var descendant in HiveClient.Instance.GetAvailableChildResources(item.Id)) { newIncludedResources.Add(descendant); } } } private void BuildResourceTree(IEnumerable resources) { resourcesTreeView.Nodes.Clear(); if (!resources.Any()) return; resourcesTreeView.BeforeCheck -= resourcesTreeView_BeforeCheck; resourcesTreeView.AfterCheck -= resourcesTreeView_AfterCheck; var mainResources = new HashSet(resources.OfType().Where(x => x.ParentResourceId == null)); var parentedMainResources = new HashSet(resources.OfType() .Where(x => x.ParentResourceId.HasValue && !resources.Select(y => y.Id).Contains(x.ParentResourceId.Value))); mainResources.UnionWith(parentedMainResources); var subResources = new HashSet(resources.Except(mainResources)); var addedAssignments = newAssignedResources.Except(assignedResources); var removedAssignments = assignedResources.Except(newAssignedResources); var addedIncludes = newIncludedResources.Except(includedResources); var removedIncludes = includedResources.Except(newIncludedResources); TreeNode currentNode = null; Resource currentResource = null; var stack = new Stack(mainResources.OrderByDescending(x => x.Name)); while (stack.Any()) { var newResource = stack.Pop(); var newNode = new TreeNode(newResource.Name) { Tag = newResource }; // search for parent node of newNode and save in currentNode // necessary since newNodes (stack top items) might be siblings // or grand..grandparents of previous node (currentNode) while (currentNode != null && newResource.ParentResourceId != currentResource.Id) { currentNode = currentNode.Parent; currentResource = currentNode == null ? null : (Resource)currentNode.Tag; } if (currentNode == null) { resourcesTreeView.Nodes.Add(newNode); } else { currentNode.Nodes.Add(newNode); } if (newAssignedResources.Select(x => x.Id).Contains(newResource.Id)) { newNode.Checked = true; } else if (newIncludedResources.Select(x => x.Id).Contains(newResource.Id)) { newNode.Checked = true; newNode.ForeColor = grayTextColor; } if (includedResources.Select(x => x.Id).Contains(newResource.Id) && newIncludedResources.Select(x => x.Id).Contains(newResource.Id)) { newNode.Text += INCLUDED_TAG; } else if (addedIncludes.Select(x => x.Id).Contains(newResource.Id)) { newNode.BackColor = addedIncludeColor; newNode.ForeColor = grayTextColor; newNode.Text += ADDED_INCLUDE_TAG; } else if (removedIncludes.Select(x => x.Id).Contains(newResource.Id)) { newNode.BackColor = removedIncludeColor; newNode.Text += REMOVED_INCLUDE_TAG; } if (addedAssignments.Select(x => x.Id).Contains(newResource.Id)) { newNode.BackColor = addedAssignmentColor; newNode.ForeColor = controlTextColor; newNode.Text += ADDED_SELECTION_TAG; } else if (removedAssignments.Select(x => x.Id).Contains(newResource.Id)) { newNode.BackColor = removedAssignmentColor; newNode.ForeColor = controlTextColor; newNode.Text += REMOVED_SELECTION_TAG; } if (newResource is Slave) { newNode.ImageIndex = slaveImageIndex; } else { newNode.ImageIndex = slaveGroupImageIndex; var childResources = subResources.Where(x => x.ParentResourceId == newResource.Id); if (childResources.Any()) { foreach (var resource in childResources.OrderByDescending(x => x.Name)) { subResources.Remove(resource); stack.Push(resource); } currentNode = newNode; currentResource = newResource; } } newNode.SelectedImageIndex = newNode.ImageIndex; } var singleSlaves = subResources.OfType(); if (singleSlaves.Any()) { var additionalNode = new TreeNode(additionalSlavesGroupName) { ForeColor = SystemColors.GrayText, Tag = new SlaveGroup() { Name = additionalSlavesGroupName, Description = additionalSlavesGroupDescription } }; foreach (var slave in singleSlaves.OrderBy(x => x.Name)) { var slaveNode = new TreeNode(slave.Name) { Tag = slave }; additionalNode.Nodes.Add(slaveNode); } resourcesTreeView.Nodes.Add(additionalNode); } ExpandResourceNodesOfInterest(resourcesTreeView.Nodes); resourcesTreeView.BeforeCheck += resourcesTreeView_BeforeCheck; resourcesTreeView.AfterCheck += resourcesTreeView_AfterCheck; } private void ExpandResourceNodesOfInterest(TreeNodeCollection nodes) { foreach(TreeNode n in nodes) { Resource r = (Resource)n.Tag; if(n.Nodes.Count > 0) { if(HiveClient.Instance.GetAvailableChildResources(r.Id).OfType().Any() || HiveClient.Instance.GetAvailableChildResources(r.Id).OfType().Intersect(assignedResources.Union(newAssignedResources)).Any()) { n.Expand(); ExpandResourceNodesOfInterest(n.Nodes); } else { n.Collapse(); } } else { n.Collapse(); } } } private void CollapseSlaveOnlyNodes(TreeNode tn) { if (!(tn.Tag is Resource)) return; Resource r = (Resource)tn.Tag; if (HiveClient.Instance.ResourceDescendants.ContainsKey(r.Id)) { if (HiveClient.Instance.GetAvailableChildResources(r.Id).OfType().Any()) { tn.Expand(); foreach (TreeNode n in tn.Nodes) CollapseSlaveOnlyNodes(n); } else { tn.Collapse(); } } } private void ExtractStatistics(Resource resource = null) { HashSet newAssignedSlaves = new HashSet(newAssignedResources.OfType()); foreach (var slaveGroup in newAssignedResources.OfType()) { foreach (var slave in HiveClient.Instance.GetAvailableChildResources(slaveGroup.Id).OfType()) { newAssignedSlaves.Add(slave); } } HashSet selectedSlaves = null; if (resource != null) { var slaveGroup = resource as SlaveGroup; if (slaveGroup != null) { selectedSlaves = new HashSet(HiveClient.Instance.GetAvailableChildResources(slaveGroup.Id).OfType()); } else { selectedSlaves = new HashSet(new[] { resource as Slave }); } } else { selectedSlaves = newAssignedSlaves; } int sumCores = selectedSlaves.Sum(x => x.Cores.GetValueOrDefault()); int sumFreeCores = selectedSlaves.Sum(x => x.FreeCores.GetValueOrDefault()); double sumMemory = selectedSlaves.Sum(x => x.Memory.GetValueOrDefault()) / 1024.0; double sumFreeMemory = selectedSlaves.Sum(x => x.FreeMemory.GetValueOrDefault()) / 1024.0; coresSummaryLabel.Text = $"{sumCores} Total ({sumFreeCores} Free / {sumCores - sumFreeCores} Used)"; memorySummaryLabel.Text = $"{sumMemory:0.00} GB Total ({sumFreeMemory:0.00} GB Free / {(sumMemory - sumFreeMemory):0.00} GB Used)"; } private void StyleTreeNode(TreeNode n, string name) { n.Text = name; n.BackColor = Color.Transparent; n.ForeColor = Color.Black; } private void ResetTreeNodes(TreeNodeCollection nodes) { foreach (TreeNode n in nodes) { string name = ""; if (n.Tag is Project) name = ((Project)n.Tag).Name; else if (n.Tag is Resource) name = ((Resource)n.Tag).Name; StyleTreeNode(n, name); if (n.Nodes.Count > 0) { ResetTreeNodes(n.Nodes); } } } private void SetTreeNodes(TreeNodeCollection nodes) { var addedAssignments = newAssignedResources.Except(assignedResources); var removedAssignments = assignedResources.Except(newAssignedResources); var addedIncludes = newIncludedResources.Except(includedResources); var removedIncludes = includedResources.Except(newIncludedResources); foreach (TreeNode n in nodes) { if(n.Tag is Resource) { // reset var resource = (Resource)n.Tag; n.Text = resource.Name; n.BackColor = Color.Transparent; n.ForeColor = Color.Black; n.Checked = false; // add additional info if (newAssignedResources.Select(x => x.Id).Contains(resource.Id)) { n.Checked = true; } else if (newIncludedResources.Select(x => x.Id).Contains(resource.Id)) { n.Checked = true; n.ForeColor = grayTextColor; } if (includedResources.Select(x => x.Id).Contains(resource.Id) && newIncludedResources.Select(x => x.Id).Contains(resource.Id)) { n.Text += INCLUDED_TAG; } else if (addedIncludes.Select(x => x.Id).Contains(resource.Id)) { n.BackColor = addedIncludeColor; n.ForeColor = grayTextColor; n.Text += ADDED_INCLUDE_TAG; } else if (removedIncludes.Select(x => x.Id).Contains(resource.Id)) { n.BackColor = removedIncludeColor; n.Text += REMOVED_INCLUDE_TAG; } if (addedAssignments.Select(x => x.Id).Contains(resource.Id)) { n.BackColor = addedAssignmentColor; n.ForeColor = controlTextColor; n.Text += ADDED_SELECTION_TAG; } else if (removedAssignments.Select(x => x.Id).Contains(resource.Id)) { n.BackColor = removedAssignmentColor; n.ForeColor = controlTextColor; n.Text += REMOVED_SELECTION_TAG; } } if(n.Nodes.Count > 0) { SetTreeNodes(n.Nodes); } } } #endregion #region Events public event EventHandler SelectedProjectChanged; private void OnSelectedProjectChanged() { SelectedProjectChanged?.Invoke(this, EventArgs.Empty); } public event EventHandler AssignedResourcesChanged; private void OnAssignedResourcesChanged() { AssignedResourcesChanged?.Invoke(this, EventArgs.Empty); } public event EventHandler ProjectsTreeViewDoubleClicked; private void OnProjectsTreeViewDoubleClicked() { ProjectsTreeViewDoubleClicked?.Invoke(this, EventArgs.Empty); } #endregion } }