Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2839: fixed handling of project related updates for project owners, who are no admins and do not own parent projects

File size: 21.4 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      Guid? parentProjectId = null;
201
202      if(selectedProject == null && !IsAdmin()) {
203        MessageBox.Show(
204          "You are not allowed to add a root project - please select a parent project.",
205          "HeuristicLab Hive Administrator",
206          MessageBoxButtons.OK,
207          MessageBoxIcon.Information);
208        return;
209      }
210     
211      if (selectedProject != null && selectedProject.Id == Guid.Empty) {
212        MessageBox.Show(
213          "You cannot add a project to a not yet stored project.",
214          "HeuristicLab Hive Administrator",
215          MessageBoxButtons.OK,
216          MessageBoxIcon.Information);
217        return;
218      }
219
220      if (selectedProject != null) parentProjectId = selectedProject.Id;
221      var project = new Project {
222        Name = "New Project",
223        OwnerUserId = UserInformation.Instance.User.Id,
224        ParentProjectId = parentProjectId
225      };
226
227      SelectedProject = project;
228      Content.Add(project);
229    }
230
231    private async void removeButton_Click(object sender, EventArgs e) {
232      if (selectedProject == null) return;
233
234      lock (locker) {
235        // for details go to ChangeSelectedProject(..)
236        if (!removeButton.Enabled) return;
237        removeButton.Enabled = false;
238      }
239
240      // double check of ChangeSelectedProject(..):
241      // if the user is no admin nor owner of a parent project
242      if (!IsAdmin() && !HiveAdminClient.Instance.CheckOwnershipOfParentProject(selectedProject, UserInformation.Instance.User.Id)) {
243        MessageBox.Show(
244          "Only admins and owners of parent projects are allowed to delete this project.",
245          "HeuristicLab Hive Administrator",
246          MessageBoxButtons.OK,
247          MessageBoxIcon.Error);
248        return;
249      }           
250
251
252      if (Content.Any(x => x.ParentProjectId == selectedProject.Id)
253          || HiveAdminClient.Instance.ProjectDescendants[selectedProject.Id].Any()) {
254        MessageBox.Show(
255          "Only empty projects can be deleted.",
256          "HeuristicLab Hive Administrator",
257          MessageBoxButtons.OK,
258          MessageBoxIcon.Error);
259        return;
260      }
261
262      if (HiveAdminClient.Instance.Jobs.ContainsKey(selectedProject.Id)
263        && HiveAdminClient.Instance.Jobs[selectedProject.Id] != null
264        && HiveAdminClient.Instance.Jobs[selectedProject.Id].Any()) {
265        MessageBox.Show(
266          "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.",
267          "HeuristicLab Hive Administrator",
268          MessageBoxButtons.OK,
269          MessageBoxIcon.Error);
270        return;
271      }
272
273      var result = MessageBox.Show(
274        "Do you really want to delete " + selectedProject.Name + "?",
275        "HeuristicLab Hive Administrator",
276        MessageBoxButtons.YesNo,
277        MessageBoxIcon.Question);
278
279      if (result == DialogResult.Yes) {
280        await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
281          action: () => {
282            RemoveProject(selectedProject); 
283          },
284          finallyCallback: () => {
285            removeButton.Enabled = true;
286          });
287      }
288    }
289
290    private async void saveProjectButton_Click(object sender, EventArgs e) {
291      lock (locker) {
292        if (!saveProjectButton.Enabled) return;
293        saveProjectButton.Enabled = false;
294      }
295
296      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
297        action: () => {
298          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
299          foreach (var project in projectsToSave)
300            project.Store();
301          UpdateProjects();
302        },
303        finallyCallback: () => saveProjectButton.Enabled = true);
304
305      OnContentChanged();
306    }
307
308    private void projectsTreeView_MouseDown(object sender, MouseEventArgs e) {
309      var node = projectsTreeView.GetNodeAt(e.Location);
310      if (node == null) return;
311      var p = (Project)node.Tag;
312      if(!HiveAdminClient.Instance.DisabledParentProjects.Contains(p)) ChangeSelectedProjectNode(node);
313    }
314
315    private void projectsTreeView_BeforeSelect(object sender, TreeViewCancelEventArgs e) {
316      e.Cancel = true;
317    }
318
319    private void projectsTreeView_DragDrop(object sender, DragEventArgs e) {
320      if (e.Effect == DragDropEffects.None) return;
321
322      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
323      if (sourceNode == null) return;
324      var sourceProject = ((Project)sourceNode.Tag);
325      if (sourceProject == null) return;
326
327      var treeView = (TreeView)sender;
328      if (sourceNode.TreeView != treeView) return;
329
330      var targetPoint = treeView.PointToClient(new Point(e.X, e.Y));
331      var targetNode = treeView.GetNodeAt(targetPoint);
332
333      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
334
335      if (!HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
336        MessageBox.Show(
337          "You cannot drag projects to this project.",
338          "HeuristicLab Hive Administrator",
339          MessageBoxButtons.OK,
340          MessageBoxIcon.Information);
341        return;
342      }
343
344      if (sourceNode.Parent == null) {
345        treeView.Nodes.Remove(sourceNode);
346      } else {
347        sourceNode.Parent.Nodes.Remove(sourceNode);
348        sourceProject.ParentProjectId = null;
349      }
350
351      if (targetNode == null) {
352        treeView.Nodes.Add(sourceNode);
353      } else if(targetProject.Id != Guid.Empty) {
354        targetNode.Nodes.Add(sourceNode);
355        sourceProject.ParentProjectId = targetProject.Id;
356      }
357
358      SelectedProject = sourceProject;
359      OnContentChanged();
360    }
361
362    private void projectsTreeView_ItemDrag(object sender, ItemDragEventArgs e) {
363      var sourceNode = (TreeNode)e.Item;
364      if (IsAuthorized((Project)sourceNode.Tag))
365        DoDragDrop(sourceNode, DragDropEffects.All);
366    }
367
368    private void projectsTreeView_DragEnterOver(object sender, DragEventArgs e) {
369      e.Effect = DragDropEffects.Move;
370
371      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
372      var sourceProject = ((Project)sourceNode.Tag);
373
374      var targetPoint = projectsTreeView.PointToClient(new Point(e.X, e.Y));
375      var targetNode = projectsTreeView.GetNodeAt(targetPoint);
376      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
377
378      if ((!IsAdmin() && (targetNode == null || targetProject == null))
379      || sourceNode == null
380      || sourceProject == null
381      || sourceNode == targetNode
382      || !HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
383        e.Effect = DragDropEffects.None;
384      }
385    }
386
387    private void projectsTreeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) {
388      e.Action = DragAction.Continue;
389    }
390    #endregion
391
392    #region Helpers
393    private void BuildProjectTree(IEnumerable<Project> projects) {
394      projectsTreeView.Nodes.Clear();
395      if (!projects.Any()) return;
396
397      var disabledParentProjects = HiveAdminClient.Instance.DisabledParentProjects;
398      var mainProjects = new HashSet<Project>(projects.Where(x => x.ParentProjectId == null));
399      //var parentedMainProjects = new HashSet<Project>(projects
400      //  .Where(x => x.ParentProjectId.HasValue
401      //  && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value)));
402      //mainProjects.UnionWith(parentedMainProjects);
403      var mainDisabledParentProjects = new HashSet<Project>(disabledParentProjects.Where(x => x.ParentProjectId == null || x.ParentProjectId == Guid.Empty));
404      mainProjects.UnionWith(mainDisabledParentProjects);
405      var subProbjects = new HashSet<Project>(projects.Union(disabledParentProjects).Except(mainProjects));
406
407      var stack = new Stack<Project>(mainProjects.OrderByDescending(x => x.Name));
408      if (selectedProject != null) SelectedProject = projects.Where(x => x.Id == selectedProject.Id).FirstOrDefault();
409      bool nodeSelected = false;
410
411      TreeNode currentNode = null;
412      Project currentProject = null;
413
414      while (stack.Any()) {
415        var newProject = stack.Pop();
416        var newNode = new TreeNode(newProject.Name) { Tag = newProject };
417        StyleTreeNode(newNode, newProject);
418     
419        if (selectedProject == null && !disabledParentProjects.Contains(newProject)) {
420          SelectedProject = newProject;
421        }
422        if (!nodeSelected && selectedProject != null && selectedProject.Id == newProject.Id) {
423          newNode.BackColor = selectedBackColor;
424          newNode.ForeColor = selectedForeColor;
425          newNode.Text += SELECTED_TAG;
426          nodeSelected = true;
427        }
428
429        // search for parent node of newNode and save in currentNode
430        // necessary since newNodes (stack top items) might be siblings
431        // or grand..grandparents of previous node (currentNode)
432        while (currentNode != null && newProject.ParentProjectId != currentProject.Id) {
433          currentNode = currentNode.Parent;
434          currentProject = currentNode == null ? null : (Project)currentNode.Tag;
435        }
436
437        if (currentNode == null) {
438          projectsTreeView.Nodes.Add(newNode);
439          newNode.ImageIndex = greenFlagImageIndex;
440        } else {
441          currentNode.Nodes.Add(newNode);
442          newNode.ImageIndex = redFlagImageIndex;
443        }
444
445        newNode.SelectedImageIndex = newNode.ImageIndex;
446
447        if (disabledParentProjects.Contains(newProject)) {
448          newNode.Checked = false;
449          newNode.ForeColor = grayTextColor;
450        }
451
452        var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id);
453        if (childProjects.Any()) {
454          foreach (var project in childProjects.OrderByDescending(x => x.Name)) {
455            subProbjects.Remove(project);
456            stack.Push(project);
457          }
458          currentNode = newNode;
459          currentProject = newProject;
460        }
461      }
462
463      projectsTreeView.ExpandAll();
464    }
465
466    private void StyleTreeNode(TreeNode n, Project p) {
467      n.Text = p.Name;
468      n.BackColor = Color.Transparent;
469      n.ForeColor = Color.Black;
470
471      if (HiveAdminClient.Instance.DisabledParentProjects.Select(x => x.Id).Contains(p.Id)) {
472        n.ForeColor = grayTextColor;
473      } else if (p.Id == Guid.Empty) {
474        n.Text += NOT_STORED_TAG;
475      } else if (p.Modified) {
476        n.Text += CHANGES_NOT_STORED_TAG;
477      }
478    }
479
480    private void ResetTreeNodes(TreeNodeCollection nodes) {
481      foreach (TreeNode n in nodes) {
482        StyleTreeNode(n, (Project)n.Tag);
483        if (n.Nodes.Count > 0) {
484          ResetTreeNodes(n.Nodes);
485        }
486      }
487    }
488
489    private void ChangeSelectedProject(Project project) {
490      projectView.Content = project;
491      projectPermissionsView.Content = project;
492      projectResourcesView.Content = project;
493      projectJobsView.Content = project;
494
495      bool projectIsNew = project != null && project.Id == Guid.Empty;
496      bool locked = project == null || projectIsNew;
497
498      // if the project is new, or:
499      // the project does have any descendant projects and
500      // the user is an admin or owner of a parent project
501      bool deleteEnabled = project != null && (
502        projectIsNew
503        ||  (!HiveAdminClient.Instance.ProjectDescendants[project.Id].Any()
504              && (HiveAdminClient.Instance.CheckOwnershipOfParentProject(project, UserInformation.Instance.User.Id)
505              || IsAdmin())
506            )
507      );
508
509      bool addEnabled = !locked;
510      if (!IsAdmin() && project == null) addEnabled = false;
511
512      bool saveEnabled = project != null;
513      if (!IsAdmin() && !project.ParentProjectId.HasValue) saveEnabled = false;
514
515      addButton.Enabled = addEnabled;
516      removeButton.Enabled = deleteEnabled;
517      saveProjectButton.Enabled = saveEnabled;
518      projectPermissionsView.Locked = locked;
519      projectResourcesView.Locked = locked;
520      projectJobsView.Locked = locked;
521      selectedProject = project;
522    }
523
524    private void ChangeSelectedProjectNode(TreeNode projectNode) {
525      if (projectNode == null) return;
526      SelectedProject = (Project)projectNode.Tag;
527      ResetTreeNodes(projectsTreeView.Nodes);
528      projectNode.BackColor = selectedBackColor;
529      projectNode.ForeColor = selectedForeColor;
530      projectNode.Text += SELECTED_TAG;
531    }
532
533    private void UpdateProjects() {
534      try {
535        HiveAdminClient.Instance.Refresh();
536        Content = HiveAdminClient.Instance.Projects;
537      } catch (AnonymousUserException) {
538        ShowHiveInformationDialog();
539      }
540    }
541
542    private void RemoveProject(Project project) {
543      if (project == null) return;
544
545      try {
546        if (project.Id != Guid.Empty) {
547          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
548          foreach (var p in projectsToSave)
549            p.Store();
550          HiveAdminClient.Delete(project);
551          UpdateProjects();
552        } else {
553          Content.Remove(project);
554        }
555      } catch (AnonymousUserException) {
556        ShowHiveInformationDialog();
557      }
558    }
559
560    private bool IsAuthorized(Project project) {
561      return project != null && UserInformation.Instance.UserExists;
562    }
563
564    private bool IsAdmin() {
565      return HiveRoles.CheckAdminUserPermissions();
566    }
567
568    private void ShowHiveInformationDialog() {
569      if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog);
570      else {
571        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
572          dialog.ShowDialog(this);
573        }
574      }
575    }
576    #endregion
577  }
578}
Note: See TracBrowser for help on using the repository browser.