Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2839_HiveProjectManagement/HeuristicLab.Clients.Hive.Administrator/3.3/Views/ProjectsView.cs @ 16043

Last change on this file since 16043 was 16043, checked in by jzenisek, 6 years ago

#2839: adapted handling of project start/end boundaries for non-admins

File size: 21.5 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
46    private readonly Color selectedBackColor = Color.DodgerBlue;
47    private readonly Color selectedForeColor = Color.White;
48    private readonly Color grayTextColor = SystemColors.GrayText;
49
50    private Project selectedProject = null;
51    public Project SelectedProject {
52      get { return selectedProject; }
53      set { if (selectedProject != value) ChangeSelectedProject(value); }
54    }
55
56    private readonly object locker = new object();
57
58    public new IItemList<Project> Content {
59      get { return (IItemList<Project>)base.Content; }
60      set { base.Content = value; }
61    }
62
63    public ProjectsView() {
64      InitializeComponent();
65
66      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagGreen);
67      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagRed);
68
69      HiveAdminClient.Instance.Refreshing += HiveAdminClient_Instance_Refreshing;
70      HiveAdminClient.Instance.Refreshed += HiveAdminClient_Instance_Refreshed;
71      AccessClient.Instance.Refreshing += AccessClient_Instance_Refreshing;
72      AccessClient.Instance.Refreshed += AccessClient_Instance_Refreshed;
73    }
74
75    #region Overrides
76    protected override void OnClosing(FormClosingEventArgs e) {
77      AccessClient.Instance.Refreshed -= AccessClient_Instance_Refreshed;
78      AccessClient.Instance.Refreshing -= AccessClient_Instance_Refreshing;
79      HiveAdminClient.Instance.Refreshed -= HiveAdminClient_Instance_Refreshed;
80      HiveAdminClient.Instance.Refreshing -= HiveAdminClient_Instance_Refreshing;
81      base.OnClosing(e);
82    }
83
84    protected override void RegisterContentEvents() {
85      base.RegisterContentEvents();
86      Content.ItemsAdded += Content_ItemsAdded;
87      Content.ItemsRemoved += Content_ItemsRemoved;
88    }
89
90    protected override void DeregisterContentEvents() {
91      Content.ItemsRemoved -= Content_ItemsRemoved;
92      Content.ItemsAdded -= Content_ItemsAdded;
93      base.DeregisterContentEvents();
94    }
95
96    protected override void OnContentChanged() {
97      base.OnContentChanged();
98      if (Content == null) {
99        projectsTreeView.Nodes.Clear();
100        projectView.Content = null;
101        projectPermissionsView.Content = null;
102        projectResourcesView.Content = null;
103        projectJobsView.Content = null;
104      } else {
105        BuildProjectTree(Content);
106      }
107    }
108
109    protected override void SetEnabledStateOfControls() {
110      base.SetEnabledStateOfControls();
111      bool enabled = Content != null && !Locked && !ReadOnly;
112      refreshButton.Enabled = enabled;
113      //addButton.Enabled = enabled;
114      //removeButton.Enabled = enabled;
115      //saveProjectButton.Enabled = enabled;
116      projectView.Enabled = enabled;
117      projectPermissionsView.Enabled = enabled;
118      projectResourcesView.Enabled = enabled;
119      projectJobsView.Enabled = enabled;
120    }
121    #endregion
122
123    #region Event Handlers
124    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
125      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsAdded, sender, e);
126      else {
127        OnContentChanged();
128      }
129    }
130
131    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
132      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsRemoved, sender, e);
133      else {
134        OnContentChanged();
135      }
136    }
137
138    private void ProjectViewContent_PropertyChanged(object sender, PropertyChangedEventArgs e) {
139      if (InvokeRequired) Invoke((Action<object, PropertyChangedEventArgs>)ProjectViewContent_PropertyChanged, sender, e);
140      else {
141        OnContentChanged();
142      }
143    }
144
145    private void HiveAdminClient_Instance_Refreshing(object sender, EventArgs e) {
146      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshing, sender, e);
147      else {
148        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
149        mainForm.AddOperationProgressToView(this, "Refreshing ...");
150        SetEnabledStateOfControls();
151      }
152    }
153
154    private void HiveAdminClient_Instance_Refreshed(object sender, EventArgs e) {
155      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshed, sender, e);
156      else {
157        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
158        mainForm.RemoveOperationProgressFromView(this);
159        SetEnabledStateOfControls();
160      }
161    }
162
163    private void AccessClient_Instance_Refreshing(object sender, EventArgs e) {
164      if (InvokeRequired) Invoke((Action<object, EventArgs>)AccessClient_Instance_Refreshing, sender, e);
165      else {
166        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
167        mainForm.AddOperationProgressToView(this, "Refreshing ...");
168        SetEnabledStateOfControls();
169      }
170    }
171
172    private void AccessClient_Instance_Refreshed(object sender, EventArgs e) {
173      if (InvokeRequired) Invoke((Action<object, EventArgs>)AccessClient_Instance_Refreshed, sender, e);
174      else {
175        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
176        mainForm.RemoveOperationProgressFromView(this);
177        SetEnabledStateOfControls();
178      }
179    }
180
181    private async void ProjectsView_Load(object sender, EventArgs e) {
182      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
183        action: () => UpdateProjects());
184    }
185
186    private async void refreshButton_Click(object sender, EventArgs e) {
187      lock (locker) {
188        if (!refreshButton.Enabled) return;
189        refreshButton.Enabled = false;
190      }
191
192      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
193        action: () => UpdateProjects(),
194        finallyCallback: () => {
195          refreshButton.Enabled = true;
196        });
197    }
198
199    private void addButton_Click(object sender, EventArgs e) {     
200
201      if (selectedProject == null && !IsAdmin()) {
202        MessageBox.Show(
203          "You are not allowed to add a root project - please select a parent project.",
204          "HeuristicLab Hive Administrator",
205          MessageBoxButtons.OK,
206          MessageBoxIcon.Information);
207        return;
208      }
209     
210      if (selectedProject != null && selectedProject.Id == Guid.Empty) {
211        MessageBox.Show(
212          "You cannot add a project to a not yet stored project.",
213          "HeuristicLab Hive Administrator",
214          MessageBoxButtons.OK,
215          MessageBoxIcon.Information);
216        return;
217      }
218
219      var project = new Project {
220        Name = "New Project",
221        OwnerUserId = UserInformation.Instance.User.Id,       
222      };
223      if(selectedProject != null) {
224        project.ParentProjectId = selectedProject.Id;
225        project.EndDate = selectedProject.EndDate;
226      }
227
228      SelectedProject = project;
229      Content.Add(project);
230    }
231
232    private async void removeButton_Click(object sender, EventArgs e) {
233      if (selectedProject == null) return;
234
235      lock (locker) {
236        // for details go to ChangeSelectedProject(..)
237        if (!removeButton.Enabled) return;
238        removeButton.Enabled = false;
239      }
240
241      // double check of ChangeSelectedProject(..):
242      // if the user is no admin nor owner of a parent project
243      if (!IsAdmin() && !HiveAdminClient.Instance.CheckOwnershipOfParentProject(selectedProject, UserInformation.Instance.User.Id)) {
244        MessageBox.Show(
245          "Only admins and owners of parent projects are allowed to delete this project.",
246          "HeuristicLab Hive Administrator",
247          MessageBoxButtons.OK,
248          MessageBoxIcon.Error);
249        return;
250      }           
251
252
253      if (Content.Any(x => x.ParentProjectId == selectedProject.Id)
254          || HiveAdminClient.Instance.ProjectDescendants[selectedProject.Id].Any()) {
255        MessageBox.Show(
256          "Only empty projects can be deleted.",
257          "HeuristicLab Hive Administrator",
258          MessageBoxButtons.OK,
259          MessageBoxIcon.Error);
260        return;
261      }
262
263      if (HiveAdminClient.Instance.Jobs.ContainsKey(selectedProject.Id)
264        && HiveAdminClient.Instance.Jobs[selectedProject.Id] != null
265        && HiveAdminClient.Instance.Jobs[selectedProject.Id].Any()) {
266        MessageBox.Show(
267          "There are " + HiveAdminClient.Instance.Jobs[selectedProject.Id].Count + " jobs using this project and/or child-projects. It is necessary to delete them before the project.",
268          "HeuristicLab Hive Administrator",
269          MessageBoxButtons.OK,
270          MessageBoxIcon.Error);
271        return;
272      }
273
274      var result = MessageBox.Show(
275        "Do you really want to delete " + selectedProject.Name + "?",
276        "HeuristicLab Hive Administrator",
277        MessageBoxButtons.YesNo,
278        MessageBoxIcon.Question);
279
280      if (result == DialogResult.Yes) {
281        await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
282          action: () => {
283            RemoveProject(selectedProject); 
284          },
285          finallyCallback: () => {
286            removeButton.Enabled = true;
287          });
288      }
289    }
290
291    private async void saveProjectButton_Click(object sender, EventArgs e) {
292      lock (locker) {
293        if (!saveProjectButton.Enabled) return;
294        saveProjectButton.Enabled = false;
295      }
296
297      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
298        action: () => {
299          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
300          foreach (var project in projectsToSave)
301            project.Store();
302          UpdateProjects();
303        },
304        finallyCallback: () => saveProjectButton.Enabled = true);
305
306      OnContentChanged();
307    }
308
309    private void projectsTreeView_MouseDown(object sender, MouseEventArgs e) {
310      var node = projectsTreeView.GetNodeAt(e.Location);
311      if (node == null) return;
312      var p = (Project)node.Tag;
313      if(!HiveAdminClient.Instance.DisabledParentProjects.Contains(p)) ChangeSelectedProjectNode(node);
314    }
315
316    private void projectsTreeView_BeforeSelect(object sender, TreeViewCancelEventArgs e) {
317      e.Cancel = true;
318    }
319
320    private void projectsTreeView_DragDrop(object sender, DragEventArgs e) {
321      if (e.Effect == DragDropEffects.None) return;
322
323      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
324      if (sourceNode == null) return;
325      var sourceProject = ((Project)sourceNode.Tag);
326      if (sourceProject == null) return;
327
328      var treeView = (TreeView)sender;
329      if (sourceNode.TreeView != treeView) return;
330
331      var targetPoint = treeView.PointToClient(new Point(e.X, e.Y));
332      var targetNode = treeView.GetNodeAt(targetPoint);
333
334      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
335
336      if (!HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
337        MessageBox.Show(
338          "You cannot drag projects to this project.",
339          "HeuristicLab Hive Administrator",
340          MessageBoxButtons.OK,
341          MessageBoxIcon.Information);
342        return;
343      }
344
345      if (sourceNode.Parent == null) {
346        treeView.Nodes.Remove(sourceNode);
347      } else {
348        sourceNode.Parent.Nodes.Remove(sourceNode);
349        sourceProject.ParentProjectId = null;
350      }
351
352      if (targetNode == null) {
353        treeView.Nodes.Add(sourceNode);
354      } else if(targetProject.Id != Guid.Empty) {
355        targetNode.Nodes.Add(sourceNode);
356        sourceProject.ParentProjectId = targetProject.Id;
357      }
358
359      SelectedProject = sourceProject;
360      OnContentChanged();
361    }
362
363    private void projectsTreeView_ItemDrag(object sender, ItemDragEventArgs e) {
364      var sourceNode = (TreeNode)e.Item;
365      if (IsAuthorized((Project)sourceNode.Tag))
366        DoDragDrop(sourceNode, DragDropEffects.All);
367    }
368
369    private void projectsTreeView_DragEnterOver(object sender, DragEventArgs e) {
370      e.Effect = DragDropEffects.Move;
371
372      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
373      var sourceProject = ((Project)sourceNode.Tag);
374
375      var targetPoint = projectsTreeView.PointToClient(new Point(e.X, e.Y));
376      var targetNode = projectsTreeView.GetNodeAt(targetPoint);
377      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
378
379      if ((!IsAdmin() && (targetNode == null || targetProject == null))
380      || sourceNode == null
381      || sourceProject == null
382      || sourceNode == targetNode
383      || !HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
384        e.Effect = DragDropEffects.None;
385      }
386    }
387
388    private void projectsTreeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) {
389      e.Action = DragAction.Continue;
390    }
391    #endregion
392
393    #region Helpers
394    private void BuildProjectTree(IEnumerable<Project> projects) {
395      projectsTreeView.Nodes.Clear();
396      if (!projects.Any()) return;
397
398      var disabledParentProjects = HiveAdminClient.Instance.DisabledParentProjects;
399      var mainProjects = new HashSet<Project>(projects.Where(x => x.ParentProjectId == null));
400      //var parentedMainProjects = new HashSet<Project>(projects
401      //  .Where(x => x.ParentProjectId.HasValue
402      //  && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value)));
403      //mainProjects.UnionWith(parentedMainProjects);
404      var mainDisabledParentProjects = new HashSet<Project>(disabledParentProjects.Where(x => x.ParentProjectId == null || x.ParentProjectId == Guid.Empty));
405      mainProjects.UnionWith(mainDisabledParentProjects);
406      var subProbjects = new HashSet<Project>(projects.Union(disabledParentProjects).Except(mainProjects));
407
408      var stack = new Stack<Project>(mainProjects.OrderByDescending(x => x.Name));
409      if (selectedProject != null) SelectedProject = projects.Where(x => x.Id == selectedProject.Id).FirstOrDefault();
410      bool nodeSelected = false;
411
412      TreeNode currentNode = null;
413      Project currentProject = null;
414
415      while (stack.Any()) {
416        var newProject = stack.Pop();
417        var newNode = new TreeNode(newProject.Name) { Tag = newProject };
418        StyleTreeNode(newNode, newProject);
419     
420        if (selectedProject == null && !disabledParentProjects.Contains(newProject)) {
421          SelectedProject = newProject;
422        }
423        if (!nodeSelected && selectedProject != null && selectedProject.Id == newProject.Id) {
424          newNode.BackColor = selectedBackColor;
425          newNode.ForeColor = selectedForeColor;
426          newNode.Text += SELECTED_TAG;
427          nodeSelected = true;
428        }
429
430        // search for parent node of newNode and save in currentNode
431        // necessary since newNodes (stack top items) might be siblings
432        // or grand..grandparents of previous node (currentNode)
433        while (currentNode != null && newProject.ParentProjectId != currentProject.Id) {
434          currentNode = currentNode.Parent;
435          currentProject = currentNode == null ? null : (Project)currentNode.Tag;
436        }
437
438        if (currentNode == null) {
439          projectsTreeView.Nodes.Add(newNode);
440          newNode.ImageIndex = greenFlagImageIndex;
441        } else {
442          currentNode.Nodes.Add(newNode);
443          newNode.ImageIndex = redFlagImageIndex;
444        }
445
446        newNode.SelectedImageIndex = newNode.ImageIndex;
447
448        if (disabledParentProjects.Contains(newProject)) {
449          newNode.Checked = false;
450          newNode.ForeColor = grayTextColor;
451        }
452
453        var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id);
454        if (childProjects.Any()) {
455          foreach (var project in childProjects.OrderByDescending(x => x.Name)) {
456            subProbjects.Remove(project);
457            stack.Push(project);
458          }
459          currentNode = newNode;
460          currentProject = newProject;
461        }
462      }
463
464      projectsTreeView.ExpandAll();
465    }
466
467    private void StyleTreeNode(TreeNode n, Project p) {
468      n.Text = p.Name;
469      n.BackColor = Color.Transparent;
470      n.ForeColor = Color.Black;
471
472      if (HiveAdminClient.Instance.DisabledParentProjects.Select(x => x.Id).Contains(p.Id)) {
473        n.ForeColor = grayTextColor;
474      } else if (p.Id == Guid.Empty) {
475        n.Text += NOT_STORED_TAG;
476      } else if (p.Modified) {
477        n.Text += CHANGES_NOT_STORED_TAG;
478      }
479    }
480
481    private void ResetTreeNodes(TreeNodeCollection nodes) {
482      foreach (TreeNode n in nodes) {
483        StyleTreeNode(n, (Project)n.Tag);
484        if (n.Nodes.Count > 0) {
485          ResetTreeNodes(n.Nodes);
486        }
487      }
488    }
489
490    private void ChangeSelectedProject(Project project) {
491      projectView.Content = project;
492      projectPermissionsView.Content = project;
493      projectResourcesView.Content = project;
494      projectJobsView.Content = project;
495
496      bool projectIsNew = project != null && project.Id == Guid.Empty;
497      bool locked = project == null || projectIsNew;
498
499      // if the project is new, or:
500      // the project does have any descendant projects and
501      // the user is an admin or owner of a parent project
502      bool deleteEnabled = project != null && (
503        projectIsNew
504        ||  (!HiveAdminClient.Instance.ProjectDescendants[project.Id].Any()
505              && (HiveAdminClient.Instance.CheckOwnershipOfParentProject(project, UserInformation.Instance.User.Id)
506              || IsAdmin())
507            )
508      );
509
510      bool addEnabled = !locked;
511      if(addEnabled) {
512        var now = DateTime.Now;
513        if (now < project.StartDate || now > project.EndDate) addEnabled = false;
514      }
515
516      bool saveEnabled = project != null;
517      if (!IsAdmin() && !project.ParentProjectId.HasValue) saveEnabled = false;
518
519      addButton.Enabled = addEnabled;
520      removeButton.Enabled = deleteEnabled;
521      saveProjectButton.Enabled = saveEnabled;
522      projectPermissionsView.Locked = locked;
523      projectResourcesView.Locked = locked;
524      projectJobsView.Locked = locked;
525      selectedProject = project;
526    }
527
528    private void ChangeSelectedProjectNode(TreeNode projectNode) {
529      if (projectNode == null) return;
530      SelectedProject = (Project)projectNode.Tag;
531      ResetTreeNodes(projectsTreeView.Nodes);
532      projectNode.BackColor = selectedBackColor;
533      projectNode.ForeColor = selectedForeColor;
534      projectNode.Text += SELECTED_TAG;
535    }
536
537    private void UpdateProjects() {
538      try {
539        HiveAdminClient.Instance.Refresh();
540        Content = HiveAdminClient.Instance.Projects;
541      } catch (AnonymousUserException) {
542        ShowHiveInformationDialog();
543      }
544    }
545
546    private void RemoveProject(Project project) {
547      if (project == null) return;
548
549      try {
550        if (project.Id != Guid.Empty) {
551          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
552          foreach (var p in projectsToSave)
553            p.Store();
554          HiveAdminClient.Delete(project);
555          UpdateProjects();
556        } else {
557          Content.Remove(project);
558        }
559      } catch (AnonymousUserException) {
560        ShowHiveInformationDialog();
561      }
562    }
563
564    private bool IsAuthorized(Project project) {
565      return project != null && UserInformation.Instance.UserExists;
566    }
567
568    private bool IsAdmin() {
569      return HiveRoles.CheckAdminUserPermissions();
570    }
571
572    private void ShowHiveInformationDialog() {
573      if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog);
574      else {
575        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
576          dialog.ShowDialog(this);
577        }
578      }
579    }
580    #endregion
581  }
582}
Note: See TracBrowser for help on using the repository browser.