source: trunk/HeuristicLab.Clients.Hive.Administrator/3.3/Views/ProjectsView.cs @ 16202

Last change on this file since 16202 was 16202, checked in by jzenisek, 12 months ago

#2839: fixed several bugs:

  • renamed tab "Slaves" to "Resources"
  • fixed bugs in job manager > resource selector reported by fholzinger
  • adapted client-side handling of disabled resource ancestors
  • adapted parentship check in HiveAdminClient
  • adapted seting of enabled state of controls for project details, project resources
  • introduced "inactive" tag in ProjectsView
File size: 21.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Drawing;
26using System.Linq;
27using System.Windows.Forms;
28using HeuristicLab.Clients.Access;
29using HeuristicLab.Clients.Hive.Views;
30using HeuristicLab.Collections;
31using HeuristicLab.Common.Resources;
32using HeuristicLab.Core;
33using HeuristicLab.Core.Views;
34using HeuristicLab.MainForm;
35
36namespace HeuristicLab.Clients.Hive.Administrator.Views {
37  [View("Projects View")]
38  [Content(typeof(IItemList<Project>), false)]
39  public partial class ProjectsView : ItemView, IDisposable {
40    private const int greenFlagImageIndex = 0;
41    private const int redFlagImageIndex = 1;
42    private const string SELECTED_TAG = ""; // " [selected]";
43    private const string NOT_STORED_TAG = "*"; // " [not stored]";
44    private const string CHANGES_NOT_STORED_TAG = "*"; // " [changes not stored]";
45    private const string INACTIVE_TAG = " [inactive]"; 
46
47    private readonly Color selectedBackColor = Color.DodgerBlue;
48    private readonly Color selectedForeColor = Color.White;
49    private readonly Color grayTextColor = SystemColors.GrayText;
50
51    private Project selectedProject = null;
52    public Project SelectedProject {
53      get { return selectedProject; }
54      set { if (selectedProject != value) ChangeSelectedProject(value); }
55    }
56
57    private readonly object locker = new object();
58
59    public new IItemList<Project> Content {
60      get { return (IItemList<Project>)base.Content; }
61      set { base.Content = value; }
62    }
63
64    public ProjectsView() {
65      InitializeComponent();
66
67      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagGreen);
68      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagRed);
69
70      HiveAdminClient.Instance.Refreshing += HiveAdminClient_Instance_Refreshing;
71      HiveAdminClient.Instance.Refreshed += HiveAdminClient_Instance_Refreshed;
72      AccessClient.Instance.Refreshing += AccessClient_Instance_Refreshing;
73      AccessClient.Instance.Refreshed += AccessClient_Instance_Refreshed;
74    }
75
76    #region Overrides
77    protected override void OnClosing(FormClosingEventArgs e) {
78      AccessClient.Instance.Refreshed -= AccessClient_Instance_Refreshed;
79      AccessClient.Instance.Refreshing -= AccessClient_Instance_Refreshing;
80      HiveAdminClient.Instance.Refreshed -= HiveAdminClient_Instance_Refreshed;
81      HiveAdminClient.Instance.Refreshing -= HiveAdminClient_Instance_Refreshing;
82      base.OnClosing(e);
83    }
84
85    protected override void RegisterContentEvents() {
86      base.RegisterContentEvents();
87      Content.ItemsAdded += Content_ItemsAdded;
88      Content.ItemsRemoved += Content_ItemsRemoved;
89    }
90
91    protected override void DeregisterContentEvents() {
92      Content.ItemsRemoved -= Content_ItemsRemoved;
93      Content.ItemsAdded -= Content_ItemsAdded;
94      base.DeregisterContentEvents();
95    }
96
97    protected override void OnContentChanged() {
98      base.OnContentChanged();
99      if (Content == null) {
100        projectsTreeView.Nodes.Clear();
101        projectView.Content = null;
102        projectPermissionsView.Content = null;
103        projectResourcesView.Content = null;
104        projectJobsView.Content = null;
105        selectedProject = null;
106      } else {
107        BuildProjectTree(Content);
108      }
109      SetEnabledStateOfControls();
110    }
111
112    protected override void SetEnabledStateOfControls() {
113      base.SetEnabledStateOfControls();     
114
115      bool locked = Content == null || Locked || ReadOnly;     
116      bool parentOwner = selectedProject != null && HiveAdminClient.Instance.CheckOwnershipOfParentProject(selectedProject, UserInformation.Instance.User.Id);
117      bool selectedProjectDisabled = selectedProject == null 
118                                     || selectedProject != null && selectedProject.Id == Guid.Empty;
119
120      bool selectedProjectHasJobs =
121        !selectedProjectDisabled && HiveAdminClient.Instance.Jobs.ContainsKey(selectedProject.Id)
122                                 && HiveAdminClient.Instance.Jobs[selectedProject.Id] != null
123                                 && HiveAdminClient.Instance.Jobs[selectedProject.Id].Any();
124
125      bool addLocked = locked
126                       || (selectedProject == null && !IsAdmin())
127                       || (selectedProject != null && selectedProject.Id == Guid.Empty)
128                       || (selectedProject != null && !IsAdmin() && !parentOwner && selectedProject.OwnerUserId != UserInformation.Instance.User.Id)
129                       || (selectedProject != null && (DateTime.Now < selectedProject.StartDate || DateTime.Now > selectedProject.EndDate));
130
131      bool deleteLocked = locked
132                          || !Content.Any()
133                          || selectedProject == null
134                          || Content.Any(x => x.ParentProjectId == selectedProject.Id)
135                          || selectedProjectHasJobs
136                          || (!IsAdmin() && !parentOwner);
137
138      bool saveLocked = locked
139                        || !Content.Any()
140                        || selectedProject == null
141                        || (!IsAdmin() && !parentOwner && selectedProject.OwnerUserId != UserInformation.Instance.User.Id);
142
143
144      addButton.Enabled = !addLocked;
145      removeButton.Enabled = !deleteLocked;
146      saveProjectButton.Enabled = !saveLocked;
147
148      projectView.Enabled = !locked;
149      projectPermissionsView.Enabled = !locked && !selectedProjectDisabled;
150      projectResourcesView.Enabled = !locked && !selectedProjectDisabled;
151      projectJobsView.Enabled = !locked && !selectedProjectDisabled;
152
153      projectView.Locked = locked;
154      projectPermissionsView.Locked = locked || selectedProjectDisabled;
155      projectResourcesView.Locked = locked || selectedProjectDisabled;
156      projectJobsView.Locked = locked || selectedProjectDisabled;
157    }
158    #endregion
159
160    #region Event Handlers
161    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
162      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsAdded, sender, e);
163      else {
164        OnContentChanged();
165      }
166    }
167
168    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
169      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsRemoved, sender, e);
170      else {
171        OnContentChanged();
172      }
173    }
174
175    private void ProjectViewContent_PropertyChanged(object sender, PropertyChangedEventArgs e) {
176      if (InvokeRequired) Invoke((Action<object, PropertyChangedEventArgs>)ProjectViewContent_PropertyChanged, sender, e);
177      else {
178        OnContentChanged();
179      }
180    }
181
182    private void HiveAdminClient_Instance_Refreshing(object sender, EventArgs e) {
183      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshing, sender, e);
184      else {
185        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
186        mainForm.AddOperationProgressToView(this, "Refreshing ...");
187        SetEnabledStateOfControls();
188      }
189    }
190
191    private void HiveAdminClient_Instance_Refreshed(object sender, EventArgs e) {
192      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshed, sender, e);
193      else {
194        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
195        mainForm.RemoveOperationProgressFromView(this);
196        SetEnabledStateOfControls();
197      }
198    }
199
200    private void AccessClient_Instance_Refreshing(object sender, EventArgs e) {
201      if (InvokeRequired) Invoke((Action<object, EventArgs>)AccessClient_Instance_Refreshing, sender, e);
202      else {
203        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
204        mainForm.AddOperationProgressToView(this, "Refreshing ...");
205        SetEnabledStateOfControls();
206      }
207    }
208
209    private void AccessClient_Instance_Refreshed(object sender, EventArgs e) {
210      if (InvokeRequired) Invoke((Action<object, EventArgs>)AccessClient_Instance_Refreshed, sender, e);
211      else {
212        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
213        mainForm.RemoveOperationProgressFromView(this);
214        SetEnabledStateOfControls();
215      }
216    }
217
218    private async void ProjectsView_Load(object sender, EventArgs e) {
219      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
220        action: () => UpdateProjects());
221    }
222
223    private async void refreshButton_Click(object sender, EventArgs e) {
224      lock (locker) {
225        if (!refreshButton.Enabled) return;
226        refreshButton.Enabled = false;
227      }
228
229      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
230        action: () => UpdateProjects(),
231        finallyCallback: () => {
232          refreshButton.Enabled = true;
233        });
234    }
235
236    private void addButton_Click(object sender, EventArgs e) {     
237
238      if (selectedProject == null && !IsAdmin()) {
239        MessageBox.Show(
240          "You are not allowed to add a root project - please select a parent project.",
241          "HeuristicLab Hive Administrator",
242          MessageBoxButtons.OK,
243          MessageBoxIcon.Information);
244        return;
245      }
246     
247      if (selectedProject != null && selectedProject.Id == Guid.Empty) {
248        MessageBox.Show(
249          "You cannot add a project to a not yet stored project.",
250          "HeuristicLab Hive Administrator",
251          MessageBoxButtons.OK,
252          MessageBoxIcon.Information);
253        return;
254      }
255
256      var project = new Project {
257        Name = "New Project",
258        OwnerUserId = UserInformation.Instance.User.Id,       
259      };
260      if(selectedProject != null) {
261        project.ParentProjectId = selectedProject.Id;
262        project.EndDate = selectedProject.EndDate;
263      }
264
265      SelectedProject = project;
266      Content.Add(project);
267    }
268
269    private async void removeButton_Click(object sender, EventArgs e) {
270      if (selectedProject == null) return;
271
272      lock (locker) {
273        // for details go to ChangeSelectedProject(..)
274        if (!removeButton.Enabled) return;
275        removeButton.Enabled = false;
276      }
277
278      var result = MessageBox.Show(
279        "Do you really want to delete " + selectedProject.Name + "?",
280        "HeuristicLab Hive Administrator",
281        MessageBoxButtons.YesNo,
282        MessageBoxIcon.Question);
283
284      if (result == DialogResult.Yes) {
285        await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
286          action: () => {
287            RemoveProject(selectedProject); 
288          });
289      }
290      SetEnabledStateOfControls();
291    }
292
293    private async void saveProjectButton_Click(object sender, EventArgs e) {
294      lock (locker) {
295        if (!saveProjectButton.Enabled) return;
296        saveProjectButton.Enabled = false;
297      }
298
299      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
300        action: () => {
301          //if (selectedProject != null && selectedProject.Id == Guid.Empty)
302          //  SelectedProject = HiveAdminClient.Instance.GetAvailableProjectAncestors(selectedProject.Id).LastOrDefault();
303          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
304          foreach (var project in projectsToSave)
305            project.Store();
306         
307          UpdateProjects();
308        },
309        finallyCallback: () => saveProjectButton.Enabled = true);
310
311      OnContentChanged();
312    }
313
314    private void projectsTreeView_MouseDown(object sender, MouseEventArgs e) {
315      var node = projectsTreeView.GetNodeAt(e.Location);
316      if (node == null) return;
317      var p = (Project)node.Tag;
318      if(!HiveAdminClient.Instance.DisabledParentProjects.Contains(p)) ChangeSelectedProjectNode(node);
319    }
320
321    private void projectsTreeView_BeforeSelect(object sender, TreeViewCancelEventArgs e) {
322      e.Cancel = true;
323    }
324
325    private void projectsTreeView_DragDrop(object sender, DragEventArgs e) {
326      if (e.Effect == DragDropEffects.None) return;
327
328      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
329      if (sourceNode == null) return;
330      var sourceProject = ((Project)sourceNode.Tag);
331      if (sourceProject == null) return;
332
333      var treeView = (TreeView)sender;
334      if (sourceNode.TreeView != treeView) return;
335
336      var targetPoint = treeView.PointToClient(new Point(e.X, e.Y));
337      var targetNode = treeView.GetNodeAt(targetPoint);
338
339      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
340
341      if (!HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
342        MessageBox.Show(
343          "You cannot drag projects to this project.",
344          "HeuristicLab Hive Administrator",
345          MessageBoxButtons.OK,
346          MessageBoxIcon.Information);
347        return;
348      }
349
350      if (sourceNode.Parent == null) {
351        treeView.Nodes.Remove(sourceNode);
352      } else {
353        sourceNode.Parent.Nodes.Remove(sourceNode);
354        sourceProject.ParentProjectId = null;
355      }
356
357      if (targetNode == null) {
358        treeView.Nodes.Add(sourceNode);
359      } else if(targetProject.Id != Guid.Empty) {
360        targetNode.Nodes.Add(sourceNode);
361        sourceProject.ParentProjectId = targetProject.Id;
362      }
363
364      SelectedProject = sourceProject;
365      OnContentChanged();
366    }
367
368    private void projectsTreeView_ItemDrag(object sender, ItemDragEventArgs e) {
369      var sourceNode = (TreeNode)e.Item;
370      if (IsAuthorized((Project)sourceNode.Tag))
371        DoDragDrop(sourceNode, DragDropEffects.All);
372    }
373
374    private void projectsTreeView_DragEnterOver(object sender, DragEventArgs e) {
375      e.Effect = DragDropEffects.Move;
376
377      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
378      var sourceProject = ((Project)sourceNode.Tag);
379
380      var targetPoint = projectsTreeView.PointToClient(new Point(e.X, e.Y));
381      var targetNode = projectsTreeView.GetNodeAt(targetPoint);
382      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
383
384      if ((!IsAdmin() && (targetNode == null || targetProject == null))
385      || sourceNode == null
386      || sourceProject == null
387      || sourceNode == targetNode
388      || !HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
389        e.Effect = DragDropEffects.None;
390      }
391    }
392
393    private void projectsTreeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) {
394      e.Action = DragAction.Continue;
395    }
396    #endregion
397
398    #region Helpers
399    private void BuildProjectTree(IEnumerable<Project> projects) {
400      projectsTreeView.Nodes.Clear();
401      if (!projects.Any()) return;
402
403      var disabledParentProjects = HiveAdminClient.Instance.DisabledParentProjects;
404      var mainProjects = new HashSet<Project>(projects.Where(x => x.ParentProjectId == null));
405      //var parentedMainProjects = new HashSet<Project>(projects
406      //  .Where(x => x.ParentProjectId.HasValue
407      //  && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value)));
408      //mainProjects.UnionWith(parentedMainProjects);
409      var mainDisabledParentProjects = new HashSet<Project>(disabledParentProjects.Where(x => x.ParentProjectId == null || x.ParentProjectId == Guid.Empty));
410      mainProjects.UnionWith(mainDisabledParentProjects);
411      var subProbjects = new HashSet<Project>(projects.Union(disabledParentProjects).Except(mainProjects));
412
413      var stack = new Stack<Project>(mainProjects.OrderByDescending(x => x.Name));
414      if (selectedProject != null) SelectedProject = projects.Where(x => x.Id == selectedProject.Id).FirstOrDefault();
415      bool nodeSelected = false;
416
417      TreeNode currentNode = null;
418      Project currentProject = null;
419
420      while (stack.Any()) {
421        var newProject = stack.Pop();
422        var newNode = new TreeNode(newProject.Name) { Tag = newProject };
423        StyleTreeNode(newNode, newProject);
424     
425        if (selectedProject == null && !disabledParentProjects.Contains(newProject)) {
426          SelectedProject = newProject;
427        }
428        if (!nodeSelected && selectedProject != null && selectedProject.Id == newProject.Id) {
429          newNode.BackColor = selectedBackColor;
430          newNode.ForeColor = selectedForeColor;
431          newNode.Text += SELECTED_TAG;
432          nodeSelected = true;
433        }
434
435        // search for parent node of newNode and save in currentNode
436        // necessary since newNodes (stack top items) might be siblings
437        // or grand..grandparents of previous node (currentNode)
438        while (currentNode != null && newProject.ParentProjectId != currentProject.Id) {
439          currentNode = currentNode.Parent;
440          currentProject = currentNode == null ? null : (Project)currentNode.Tag;
441        }
442
443        if (currentNode == null) {
444          projectsTreeView.Nodes.Add(newNode);
445          newNode.ImageIndex = greenFlagImageIndex;
446        } else {
447          currentNode.Nodes.Add(newNode);
448          newNode.ImageIndex = redFlagImageIndex;
449        }
450
451        newNode.SelectedImageIndex = newNode.ImageIndex;
452
453        if (disabledParentProjects.Contains(newProject)) {
454          newNode.Checked = false;
455          newNode.ForeColor = grayTextColor;
456        }
457
458        var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id);
459        if (childProjects.Any()) {
460          foreach (var project in childProjects.OrderByDescending(x => x.Name)) {
461            subProbjects.Remove(project);
462            stack.Push(project);
463          }
464          currentNode = newNode;
465          currentProject = newProject;
466        }
467      }
468
469      projectsTreeView.ExpandAll();
470    }
471
472    private void StyleTreeNode(TreeNode n, Project p) {
473      n.Text = p.Name;
474      n.BackColor = Color.Transparent;
475      n.ForeColor = Color.Black;
476
477      if (HiveAdminClient.Instance.DisabledParentProjects.Select(x => x.Id).Contains(p.Id)) {
478        n.ForeColor = grayTextColor;
479      } else {
480        if (p.Id == Guid.Empty) {
481          n.Text += NOT_STORED_TAG;
482        } else if (p.Modified) {
483          n.Text += CHANGES_NOT_STORED_TAG;
484        }
485
486        if (!IsActive(p)) {
487          n.ForeColor = grayTextColor;
488          n.Text += INACTIVE_TAG;
489        }
490      }
491    }
492
493    private void ResetTreeNodes(TreeNodeCollection nodes) {
494      foreach (TreeNode n in nodes) {
495        StyleTreeNode(n, (Project)n.Tag);
496        if (n.Nodes.Count > 0) {
497          ResetTreeNodes(n.Nodes);
498        }
499      }
500    }
501
502    private void ChangeSelectedProject(Project project) {
503      projectView.Content = project;
504      projectPermissionsView.Content = project;
505      projectResourcesView.Content = project;
506      projectJobsView.Content = project;
507      selectedProject = project;
508      SetEnabledStateOfControls();
509    }
510
511    private void ChangeSelectedProjectNode(TreeNode projectNode) {
512      if (projectNode == null) return;
513      SelectedProject = (Project)projectNode.Tag;
514      ResetTreeNodes(projectsTreeView.Nodes);
515      projectNode.BackColor = selectedBackColor;
516      projectNode.ForeColor = selectedForeColor;
517      projectNode.Text += SELECTED_TAG;
518    }
519
520    private void UpdateProjects() {
521      try {
522        HiveAdminClient.Instance.Refresh();
523        Content = HiveAdminClient.Instance.Projects;
524      } catch (AnonymousUserException) {
525        ShowHiveInformationDialog();
526      }
527    }
528
529    private void RemoveProject(Project project) {
530      if (project == null) return;
531
532      try {
533        if (project.Id != Guid.Empty) {
534          SelectedProject = HiveAdminClient.Instance.GetAvailableProjectAncestors(project.Id).LastOrDefault();
535          HiveAdminClient.Delete(project);         
536          UpdateProjects();
537        } else {         
538          SelectedProject = Content.FirstOrDefault(x => x.Id == project.ParentProjectId);
539          Content.Remove(project);
540        }
541      } catch (AnonymousUserException) {
542        ShowHiveInformationDialog();
543      }
544    }
545
546    private bool IsActive(Project project) {
547      return DateTime.Now >= project.StartDate
548             && (project.EndDate == null
549                 || DateTime.Now < project.EndDate.Value);
550    }
551
552    private bool IsAuthorized(Project project) {
553      return project != null && UserInformation.Instance.UserExists;
554    }
555
556    private bool IsAdmin() {
557      return HiveRoles.CheckAdminUserPermissions();
558    }
559
560    private void ShowHiveInformationDialog() {
561      if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog);
562      else {
563        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
564          dialog.ShowDialog(this);
565        }
566      }
567    }
568    #endregion
569  }
570}
Note: See TracBrowser for help on using the repository browser.